From 09a0dd2ce7ebc34228f6bfc59c5bc00e40b0b8c0 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 3 Feb 2016 23:14:37 -0500 Subject: [PATCH 01/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9740dedd1..39f890365 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # README for We Vote WebApp -[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) +[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a From 65daad4e7df7958f9d8a0ab7e4a38567daea0fe7 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 3 Feb 2016 23:53:36 -0500 Subject: [PATCH 02/92] update README for coverage of develop branch --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9740dedd1..52e3d12f8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # README for We Vote WebApp -[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) +[![Build +Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) +| +[![Coverage +Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=develop)](https://coveralls.io/github/wevote/WebApp?branch=develop) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a From 8ef0af3aa2a746c6a3e62954baf43715a54e4e0f Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 3 Feb 2016 23:57:21 -0500 Subject: [PATCH 03/92] working on coverage reporting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52e3d12f8..e1103c48b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) | [![Coverage -Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=develop)](https://coveralls.io/github/wevote/WebApp?branch=develop) +Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=master) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a From 612e34d19a1873097d69f0dddd56872b63af36ff Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 3 Feb 2016 23:59:57 -0500 Subject: [PATCH 04/92] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1103c48b..6629974e3 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) -| -[![Coverage -Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=master) + +[![Coverage Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=master) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a From 92e8a69fd864478b378ced8d21811ab4f7b6ad8b Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Thu, 4 Feb 2016 00:00:11 -0500 Subject: [PATCH 05/92] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6629974e3..1a5347fa8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ # README for We Vote WebApp [![Build -Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) - -[![Coverage Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=master) +Status](https://travis-ci.org/wevote/WebApp.svg?branch=master)](https://travis-ci.org/wevote/WebApp) [![Coverage Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=master) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a From 1fc56f4ba44022d400bdba3edb4f27242a0a11d7 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Fri, 12 Feb 2016 17:32:12 -0800 Subject: [PATCH 06/92] Add office display name to candidate page --- src/js/routes/Ballot/Candidate.jsx | 2 +- src/js/stores/BallotStore.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index d404adc77..09392cf9f 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -97,7 +97,7 @@ export default class Candidate extends Component {
-
Running for US House - District 12
+
{ candidate.office_display_name }
diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 871b2879f..e275272a8 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -225,6 +225,7 @@ const BallotStore = createStore({ BallotAPIWorker .candidatesRetrieve ( we_vote_id ) .then( (response) => { + var office_display_name = _ballot_store[response.office_we_vote_id]['ballot_item_display_name']; var cand_list = _ballot_store [ response.office_we_vote_id ] . candidate_list = []; @@ -235,6 +236,7 @@ const BallotStore = createStore({ var { we_vote_id: candidate_we_vote_id } = candidate; cand_list . push (candidate_we_vote_id); _ballot_store [ candidate_we_vote_id ] = shallowClone( candidate ); + _ballot_store [ candidate_we_vote_id ].office_display_name = office_display_name; promiseQueue .push ( From 480e70f32f7262c0ffd2b84ee32b4d2650bf3fd7 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 07:36:53 -0500 Subject: [PATCH 07/92] test of repo fetching from cli --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ec45a75ab..a159746f0 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,6 @@ [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) | [![Coverage Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=develop) - npm install - npm start - This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a social way to interact with ballot data. From 71b98e08647fa46d3ca0a940d2655d62826e860a Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Sat, 13 Feb 2016 05:15:46 -0800 Subject: [PATCH 08/92] Improvements to "Contributing" README. --- CONTRIBUTING.md | 51 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 136a1fbfd..c475a7af0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,51 @@ -# Contributing +# Contributing to wevote/WebApp -This outlines the proper ways to contribute to the project. +Thank you for your interest in the We Vote WebApp project. Please let us know if we can help you get started. + (contact info: https://github.com/wevote) +This README outlines the proper ways to contribute to the project. +(Feb 13, 2016 Update: This process is still a work-in-progress.) ## Pull Requests -We do not allow committing straight to the master branch of this repository. We -also have a policy of forking the repo, creating branches from your fork, -pushing your branch, and then requesting a Pull Request. +Instead of direct check-ins to this repository, we use pull requests. Here is an outline of this process: -Here is an outline of doing a pull request: +1. Fork the wevote/WebApp repository + * Go to https://github.com/wevote/WebApp and click the “Fork” button in the upper right corner of the page. You will be asked where you want to fork it to. We suggest that you fork to your personal Github page. + * See also [https://help.github.com/articles/fork-a-repo/](https://help.github.com/articles/fork-a-repo/) +2. Clone your forked repository to your local machine + * For detailed instructions, see: [Step 2: Create a local clone of your fork](https://help.github.com/articles/fork-a-repo/) + * When you create a local clone of your fork, you may need to manually add a sub-folder to the folder where github + places projects. Suggestion: name this folder something like “PersonalGitForks”. This is so that the folder named + “WebApp” does not conflict with the cloned folder from https://github.com/wevote/WebApp +3. Add the repository you forked as the upstream repository + * To keep your fork synced with wevote/WebApp, use these the command line commands: + + cd /Users///PersonalGitForks/WebApp + git remote add upstream git@github.com:wevote/WebApp.git + + * Confirm with "git remote -v" + + $ git remote -v + origin https://github.com/DaleMcGrew/WebApp.git (fetch) + origin https://github.com/DaleMcGrew/WebApp.git (push) + upstream git@github.com:wevote/WebApp.git (fetch) + upstream git@github.com:wevote/WebApp.git (push) -1. Fork the repository -2. Clone your forked repository to your machine -3. Add the repository you forked as the upstream repository - `git add remote -upstream git@github.com:wevote/WebApp.git` -4. Do a `git fetch upstream`. Once you do this, you can `git remote -a` to see -what repositores you have available to you. + * For detailed instructions, see: [Step 3: Configure Git to sync your fork](https://help.github.com/articles/fork-a-repo/) + +4. To keep your personal fork synchronized with wevote/WebApp + git fetch upstream + +Once you do this, you can `git remote -a` to see what repositories you have available to you. + * For detailed instructions, see: [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) + * If you get an error like "Permission denied (publickey)" see: [Error permission denied public key](https://help.github.com/articles/error-permission-denied-publickey/) + * http://stackoverflow.com/questions/12940626/github-error-message-permission-denied-publickey 5. You can do a `git checkout -b origin/develop develop` to bring down the develop branch from your cloned repository. 6. Once you have done so, run `git checkout develop`. It is important to think of develop branch as your master and not worry about the master branch. -7 Next, sync the develop branch with the upstream branch. `git branch + +7. Next, sync the develop branch with the upstream branch. `git branch --set-upstream-to upstream/develop`. Doing this will allow you to pull from the original repo develop branch and not your own. You won't be committing to your develop branch, so by doing this you will always be able to pull the latest from From 47b7cb5c4fd6a1d899035c7eeb05f50ac0600447 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Sat, 13 Feb 2016 05:20:06 -0800 Subject: [PATCH 09/92] Iteration on Contributing README. --- CONTRIBUTING.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c475a7af0..0ea2ce460 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,24 +19,24 @@ Instead of direct check-ins to this repository, we use pull requests. Here is an “WebApp” does not conflict with the cloned folder from https://github.com/wevote/WebApp 3. Add the repository you forked as the upstream repository * To keep your fork synced with wevote/WebApp, use these the command line commands: - - cd /Users///PersonalGitForks/WebApp - git remote add upstream git@github.com:wevote/WebApp.git - +``` +cd /Users///PersonalGitForks/WebApp +git remote add upstream git@github.com:wevote/WebApp.git +``` * Confirm with "git remote -v" - +``` $ git remote -v origin https://github.com/DaleMcGrew/WebApp.git (fetch) origin https://github.com/DaleMcGrew/WebApp.git (push) upstream git@github.com:wevote/WebApp.git (fetch) upstream git@github.com:wevote/WebApp.git (push) - +``` * For detailed instructions, see: [Step 3: Configure Git to sync your fork](https://help.github.com/articles/fork-a-repo/) - 4. To keep your personal fork synchronized with wevote/WebApp +``` git fetch upstream - -Once you do this, you can `git remote -a` to see what repositories you have available to you. +``` + * Once you do this, you can `git remote -a` to see what repositories you have available to you. * For detailed instructions, see: [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) * If you get an error like "Permission denied (publickey)" see: [Error permission denied public key](https://help.github.com/articles/error-permission-denied-publickey/) * http://stackoverflow.com/questions/12940626/github-error-message-permission-denied-publickey From d6d12ca85aaffae80ace0c0072843458d2ab535a Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Sat, 13 Feb 2016 05:21:07 -0800 Subject: [PATCH 10/92] Iteration on Contributing README. --- CONTRIBUTING.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ea2ce460..381bc5a52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,23 +19,23 @@ Instead of direct check-ins to this repository, we use pull requests. Here is an “WebApp” does not conflict with the cloned folder from https://github.com/wevote/WebApp 3. Add the repository you forked as the upstream repository * To keep your fork synced with wevote/WebApp, use these the command line commands: -``` -cd /Users///PersonalGitForks/WebApp -git remote add upstream git@github.com:wevote/WebApp.git -``` + ``` + cd /Users///PersonalGitForks/WebApp + git remote add upstream git@github.com:wevote/WebApp.git + ``` * Confirm with "git remote -v" -``` - $ git remote -v - origin https://github.com/DaleMcGrew/WebApp.git (fetch) - origin https://github.com/DaleMcGrew/WebApp.git (push) - upstream git@github.com:wevote/WebApp.git (fetch) - upstream git@github.com:wevote/WebApp.git (push) -``` + ``` + $ git remote -v + origin https://github.com/DaleMcGrew/WebApp.git (fetch) + origin https://github.com/DaleMcGrew/WebApp.git (push) + upstream git@github.com:wevote/WebApp.git (fetch) + upstream git@github.com:wevote/WebApp.git (push) + ``` * For detailed instructions, see: [Step 3: Configure Git to sync your fork](https://help.github.com/articles/fork-a-repo/) 4. To keep your personal fork synchronized with wevote/WebApp -``` - git fetch upstream -``` + ``` + git fetch upstream + ``` * Once you do this, you can `git remote -a` to see what repositories you have available to you. * For detailed instructions, see: [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) * If you get an error like "Permission denied (publickey)" see: [Error permission denied public key](https://help.github.com/articles/error-permission-denied-publickey/) From b154269ea44e77cde9e341b1962696ecff1d5efe Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 08:25:11 -0500 Subject: [PATCH 11/92] #17 - menu pushing down other components fix for the media query handling when the menu should be displayed and how wide it should be --- src/js/Application.jsx | 4 ++-- src/sass/layout/_mediaquery.scss | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index f3d125227..7ef6d23e3 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -43,7 +43,7 @@ export default class Application extends Component {
-
+
{ voter_object ?
@@ -53,7 +53,7 @@ export default class Application extends Component { }
-
+
{ this.props.children }
diff --git a/src/sass/layout/_mediaquery.scss b/src/sass/layout/_mediaquery.scss index ef361c758..7412ab295 100644 --- a/src/sass/layout/_mediaquery.scss +++ b/src/sass/layout/_mediaquery.scss @@ -62,6 +62,15 @@ } } +@media all and (max-width: 695px) { + .no-show { + display: none; + } + .col-xs-8 { + width: 100%; + } +} + @media all and (min-width : 960px) { #app { border-right: 1px solid #d4d4d4; From 5deed1a88f90af3a387e9d9e89b0c304bc356e3a Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Sat, 13 Feb 2016 05:38:56 -0800 Subject: [PATCH 12/92] Iterating on "Contributing" README --- CONTRIBUTING.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 381bc5a52..47725c8c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,23 +19,23 @@ Instead of direct check-ins to this repository, we use pull requests. Here is an “WebApp” does not conflict with the cloned folder from https://github.com/wevote/WebApp 3. Add the repository you forked as the upstream repository * To keep your fork synced with wevote/WebApp, use these the command line commands: - ``` +``` cd /Users///PersonalGitForks/WebApp git remote add upstream git@github.com:wevote/WebApp.git - ``` +``` * Confirm with "git remote -v" - ``` - $ git remote -v - origin https://github.com/DaleMcGrew/WebApp.git (fetch) - origin https://github.com/DaleMcGrew/WebApp.git (push) - upstream git@github.com:wevote/WebApp.git (fetch) - upstream git@github.com:wevote/WebApp.git (push) - ``` +``` + $ git remote -v + origin https://github.com/DaleMcGrew/WebApp.git (fetch) + origin https://github.com/DaleMcGrew/WebApp.git (push) + upstream git@github.com:wevote/WebApp.git (fetch) + upstream git@github.com:wevote/WebApp.git (push) +``` * For detailed instructions, see: [Step 3: Configure Git to sync your fork](https://help.github.com/articles/fork-a-repo/) 4. To keep your personal fork synchronized with wevote/WebApp - ``` - git fetch upstream - ``` +``` + git fetch upstream +``` * Once you do this, you can `git remote -a` to see what repositories you have available to you. * For detailed instructions, see: [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) * If you get an error like "Permission denied (publickey)" see: [Error permission denied public key](https://help.github.com/articles/error-permission-denied-publickey/) From 2710096a833bf02514cdd9b9d6f0d3e74c75318a Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 08:51:33 -0500 Subject: [PATCH 13/92] full height of the app container --- src/sass/base/_base.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sass/base/_base.scss b/src/sass/base/_base.scss index 19f6e3f70..04f69adf2 100644 --- a/src/sass/base/_base.scss +++ b/src/sass/base/_base.scss @@ -1,3 +1,8 @@ +body, html { + height: 100%; + min-height: 100%; +} + html { box-sizing: border-box; } From c0fca14f922838265ea3fdc03e6a7c8aebf9d463 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 09:58:05 -0500 Subject: [PATCH 14/92] support for size changes on phone --- src/js/components/Ballot/CandidateItem.jsx | 4 ++-- src/js/components/ItemActionbar.jsx | 14 +++++++----- src/sass/components/_candidate.scss | 26 ++++++++-------------- src/sass/components/_itemActionbar.scss | 6 +++++ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/js/components/Ballot/CandidateItem.jsx b/src/js/components/Ballot/CandidateItem.jsx index 3750e0d20..1bedb269e 100644 --- a/src/js/components/Ballot/CandidateItem.jsx +++ b/src/js/components/Ballot/CandidateItem.jsx @@ -78,7 +78,7 @@ export default class Candidate extends Component {
{/* adding inline style to img until Rob can style... */} @@ -95,7 +95,7 @@ export default class Candidate extends Component { }
-
+

{this.state.is_support ? - + Support @@ -62,7 +62,7 @@ export default class ItemActionbar extends Component { : - + Support @@ -71,7 +71,7 @@ export default class ItemActionbar extends Component { } {this.state.is_oppose ? - + Oppose @@ -79,7 +79,7 @@ export default class ItemActionbar extends Component { : - + Oppose @@ -87,9 +87,11 @@ export default class ItemActionbar extends Component { } - + + + +  Share -  Share

); diff --git a/src/sass/components/_candidate.scss b/src/sass/components/_candidate.scss index 46f0a2b66..b0f3686bc 100644 --- a/src/sass/components/_candidate.scss +++ b/src/sass/components/_candidate.scss @@ -5,33 +5,25 @@ $largeFont: 2em; .candidate { .oppose { } - .oppose-emphasis-small { + .oppose-emphasis-small, .support-emphasis-small { font-size: $smallFont; font-weight: 300; + @media all and (max-width: 480px) { + font-size: 1em; + } } - .oppose-emphasis-medium { - color: red; + .oppose-emphasis-medium, .support-emphasis-large { + color: rgba(215, 73, 55, 1); + } + .oppose-emphasis-medium, .support-emphasis-medium { font-size: $mediumFont; font-weight: 500; } - .oppose-emphasis-large { - color: red; + .oppose-emphasis-large, .support-emphasis-large { font-size: $largeFont; font-weight: 700; } .support { padding-right: 1em; } - .support-emphasis-small { - font-size: $smallFont; - font-weight: 300; - } - .support-emphasis-medium { - font-size: $mediumFont; - font-weight: 500; - } - .support-emphasis-large { - font-size: $largeFont; - font-weight: 700; - } } diff --git a/src/sass/components/_itemActionbar.scss b/src/sass/components/_itemActionbar.scss index a9c4a939e..7a0c2a22a 100644 --- a/src/sass/components/_itemActionbar.scss +++ b/src/sass/components/_itemActionbar.scss @@ -6,4 +6,10 @@ width: 25%; } + @media all and (max-width: 480px) { + .inline-phone { + font-size: .9em; + } + } + } From acb7066b2a6e0fe3cb7ab877a255b5dfeae2ed72 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 11:00:38 -0500 Subject: [PATCH 15/92] added semver to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a159746f0..5f58d0d8a 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,8 @@ You should be able to visit WebApp here: ## After Installation: Working with WebApp Day-to-Day [Read about working with WebApp on a daily basis](README_WORKING_WITH_WEB_APP.md) + +## SemVer + +We follow [SemVer](http://semver.org/) for our releases. Please read if you plan +to tag for any releases. From 54e0d14860ebd9256a837826938fd73ee5578c7f Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 12:27:36 -0500 Subject: [PATCH 16/92] major updates to doing pull requests --- CONTRIBUTING.md | 209 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 144 insertions(+), 65 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47725c8c2..d17a3931c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,73 +2,152 @@ Thank you for your interest in the We Vote WebApp project. Please let us know if we can help you get started. (contact info: https://github.com/wevote) + This README outlines the proper ways to contribute to the project. (Feb 13, 2016 Update: This process is still a work-in-progress.) ## Pull Requests -Instead of direct check-ins to this repository, we use pull requests. Here is an outline of this process: - -1. Fork the wevote/WebApp repository - * Go to https://github.com/wevote/WebApp and click the “Fork” button in the upper right corner of the page. You will be asked where you want to fork it to. We suggest that you fork to your personal Github page. - * See also [https://help.github.com/articles/fork-a-repo/](https://help.github.com/articles/fork-a-repo/) -2. Clone your forked repository to your local machine - * For detailed instructions, see: [Step 2: Create a local clone of your fork](https://help.github.com/articles/fork-a-repo/) - * When you create a local clone of your fork, you may need to manually add a sub-folder to the folder where github - places projects. Suggestion: name this folder something like “PersonalGitForks”. This is so that the folder named - “WebApp” does not conflict with the cloned folder from https://github.com/wevote/WebApp -3. Add the repository you forked as the upstream repository - * To keep your fork synced with wevote/WebApp, use these the command line commands: -``` - cd /Users///PersonalGitForks/WebApp - git remote add upstream git@github.com:wevote/WebApp.git -``` - * Confirm with "git remote -v" -``` - $ git remote -v - origin https://github.com/DaleMcGrew/WebApp.git (fetch) - origin https://github.com/DaleMcGrew/WebApp.git (push) - upstream git@github.com:wevote/WebApp.git (fetch) - upstream git@github.com:wevote/WebApp.git (push) -``` - * For detailed instructions, see: [Step 3: Configure Git to sync your fork](https://help.github.com/articles/fork-a-repo/) -4. To keep your personal fork synchronized with wevote/WebApp -``` - git fetch upstream -``` - * Once you do this, you can `git remote -a` to see what repositories you have available to you. - * For detailed instructions, see: [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) - * If you get an error like "Permission denied (publickey)" see: [Error permission denied public key](https://help.github.com/articles/error-permission-denied-publickey/) - * http://stackoverflow.com/questions/12940626/github-error-message-permission-denied-publickey -5. You can do a `git checkout -b origin/develop develop` to bring down the develop -branch from your cloned repository. -6. Once you have done so, run `git checkout develop`. It is important to think of -develop branch as your master and not worry about the master branch. - -7. Next, sync the develop branch with the upstream branch. `git branch ---set-upstream-to upstream/develop`. Doing this will allow you to pull from the -original repo develop branch and not your own. You won't be committing to your -develop branch, so by doing this you will always be able to pull the latest from -the we vote repository develop branch. -8. To start development, create a branch to work on. Do this by `git checkout -b -name-of-branch`. This will put you into the branch to start to do your -development. -9. Once you have made your changes, you can commit the changes and then push. If -you get a message about setting upstream, do ahead and make the change to -setting the branch upstream to your repository. -10. You will go to your repository and see that there is a message that you have -a branch that you can do a pull request for. Click the button and you will see -where you can add a message to your pull request. One important note is that you -want to make sure the base fork is to the develop branch of wevote/WebApp and -not to master. -11. Take a look over the changes that you are doing a pull request for. If all -looks ok, go ahead and do the pull request. - -If you have more work to do after the pull request, go ahead and create a new -branch and start to work off of that just as you did above. You can always do -a `git checkout develop` to switch back to the develop branch and sync it with -the upstream develop branch simply by doing a `git pull`. No need to worry about -going to the master branch as it will rarely be in sync. - -These are the steps to doing a pull request. You can do this from the command -line or inside of your favorite IDE. +**Things to never ever do (or at least try to avoid)** + +Especially if you have commit access to an Angular repository + +1.don't make changes to master, always start a new branch. +2.don’t merge. It messes up the commit history. +3.don’t pull upstream without a rebase (see above). git fetch and then rebase + instead (or equivalently, `git pull upstream master --rebase`). +4.don’t use `git commit -a`. You could silently commit something regrettable. Use -p instead. + +### Setting up your repository for work + +1.Install and configure git on your local machine. +2.Create a fork of wevote/WebApp.git +3.Clone your fork +`git clone https://github.com/username/WebApp.git` +4.In your local repository, set up a remote for upstream: +`$ git remote add upstream git@github.com:wevote/WebApp.git` +5.Create ssh keys: `ssh-keygen -t rsa -C "youremail@somedomain.com"` +6.`ssh-add ~/.ssh/id_rsa` +7.`pbcopy < ~/.ssh/id_rsa.pub` +8.Go paste your keys into Github, under SSH Keys for your account. +9.Set up a git client where origin is a fork of the repository (e.g. + pertrai1/WebApp), and upstream is the real deal (e.g. wevote/WebApp) +10.Before creating a branch to work in, first make sure you’re on your local + master branch `git checkout master` +11.Next, make sure that master is in sync with the upstream source of truth: + `git fetch upstream` and then `git rebase upstream/master` Or, if you prefer + `git pull upstream master --rebase` + +Note: if there’s a conflicting commit in the history of your master branch, you +can destroy your branch and replace it with a fresh copy using the command `git +checkout -B master upstream/master`. + +12. Now create a new branch `git checkout -b doc-script-changes` +13. On the new branch, make edits to the files. + +Note: Time passes, stuff changes in the upstream repo.... +Commit your changes with `git commit -p`, or git commit and individually add +files with `git add` + +To sync your changes with what's upstream, `git fetch`. + +To make sure your commit goes in at the top of everything else on the upstream +repo, rebase: `git rebase upstream/master` + +If there are conflicts, open the file and look for the diff markers, resolve, and continue. + +Send your changes to your forked copy of the repo in the appropriate branch: +`git push -f origin doc-script-changes`. + +In the web client, go to your fork of the repo, and initiate a pull request by pushing the Pull Request button. Submit the pull request! + +While the pull request is out for consideration: +Any new changes unrelated to this one should be on a brand new branch (`git +checkout -b some-new-thing`). Don't forget to check out the master branch first, otherwise you'll branch off of the current PR branch + +If you want to make changes to your earlier commit in response to comments on +the pull request, you change back to the branch that you submitted it from (`git +checkout doc-script-changes`), make any changes, then commit and push them (steps 6-9). These get automatically added to your pull request since they're in the same branch. + +If your changes are small fixes, they should not be a new commit. Instead, use +git add and then `git commit --amend` to fix up your original commit. + +If you decide to abandon a pull request, you can CLOSE the issue it created and ignore it. + +If the pull request is good, there's nothing else for you to do, besides wait for someone to accept it. + +Once the pull request is accepted (or closed) you can delete your branch from the client. Or, you can wait until you have collected a bunch of them and delete all of the obsolete ones in one go. + +Also keep in mind that `git branch -D my-branch` deletes branches only locally, to delete them from the remote repo you have to do `git push origin :my-branch` + +Moving a change between branches +Sometimes you make a change on the wrong branch. You can move it to the right branch with git stash. From the branch where you made the changes: +`git stash` +`git checkout branch-you-want-it-on` +`git stash pop` + + +### Some useful tips and tricks + +Modify your github client to pull in all the PRs and work on them +You can set up your github client to make it easy to work with submitted pull requests. + +Edit your .git/config and add the two fetch lines shown below under remote “upstream”: +[remote "upstream"] + url = git@github.com:wevote/WebApp.git + fetch = +refs/heads/*:refs/remotes/upstream/* + fetch = +refs/pull/*/head:refs/remotes/upstream/pr/* + +Now, when you fetch upstream, you’ll get references to a bunch of PRs. +check one out with `git checkout upstream/pr/3328` for example +from detached head mode, create a branch with `git branch BRANCHNAME` +fetch upstream, rebase against master, test things out. +push to your branch to verify that CI tests are green for these changes. + +when everything is green and looks legit: +`git push upstream BRANCHNAME:master` +Pull in a specific PR for testing + +Drop commits from a PR +In the branch where you created the commits you want to drop: +`$ git rebase -i upstream/master` + +This opens an editor. Delete the lines you don’t want. Then: + $ git push -f origin branchname +Pretty colors and branch name in your command line prompt +Add something like this to your .bashrc +source ~/.bash_colors + +### Git completion +`source $PATH_TO_GIT_CORE/git-completion.bash` +`source $PATH_TO_GIT_CORE/git-prompt.sh` + +### Prompt +`export +PS1="\[$Green\]\t\[$Red\]:\[$Yellow\]\W\[\033[m\]\[$Blue\]\$(__git_ps1)\[$White\]\$ +"` +And create a file .bash_colors. + +#### How to get a change from someone’s repository into your repo before it’s pushed to master +Define a remote for their github repo, e.g. +`git remote add pertrai1 https://github.com/pertrai1/WebApp.git` + +Now fetch their changes and rebase on top of the branch they have that change in: +`git fetch pertrai1` +`git rebase pertrai1/pertrai1_branchname` + +If there are collisions, these files are removed from your git commit. Hunt them down by searching for seven > characters ‘>>>>>>>’ in your project. Resolve any conflicts in your editor. git status will also show the affected files (in red, since they’re not part of a commit) + +Add the files back into tracking with `git add .` + +Carry on with the rebase: `git rebase --continue` + +#### How to get rid of garbage files that shouldn’t be in git + +Sometimes my Mac makes .DS_Store files in my git directories and I want to get rid of them: + +`$ git status` - check that you don’t have anything important that should be added first! +`$ git clean . -f` + +Caution -- if you have any new files that aren’t under git control, this will remove all of them. + From 1aa45dd5266fbc3470ce208b4df8f48bc3d8271a Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Sun, 14 Feb 2016 08:00:29 -0500 Subject: [PATCH 17/92] updates to stores etc... --- src/js/routes/Ballot/Ballot.jsx | 2 +- src/js/stores/BallotStore.js | 127 ----------------- src/js/stores/VoterStore.js | 246 +++++++------------------------- src/js/utils/service.js | 124 +++++++++++++++- 4 files changed, 174 insertions(+), 325 deletions(-) diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 58bdad8f0..7d3245a41 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -15,7 +15,7 @@ export default class Ballot extends Component { } componentDidMount () { - BallotStore.initialize( (ballot_list) => this.setState({ ballot_list }) ); + } render () { diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 871b2879f..4ceca889e 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -25,133 +25,6 @@ function ballotItemIsMeasure (we_vote_id) { } const BallotAPIWorker = { - voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { - return service.get({ - endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', - query: { text_for_map_search }, success - }); - }, - - candidatesRetrieve: function (office_we_vote_id, success ) { - return service.get({ - endpoint: 'candidatesRetrieve', - query: { office_we_vote_id }, - success - }); - }, - - // get the ballot items - voterBallotItemsRetrieve: function ( success ) { - return service.get({ - endpoint: 'voterBallotItemsRetrieve', - success - }); - }, - - positionOpposeCountForBallotItem: function (we_vote_id, success ) { - return service.get({ - endpoint: 'positionOpposeCountForBallotItem', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - // get measure support an opposition - positionSupportCountForBallotItem: function (we_vote_id, success ) { - return service.get({ - endpoint: 'positionSupportCountForBallotItem', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterPositionRetrieve: function ( ballot_item_we_vote_id, success ) { - return service.get({ - endpoint: 'voterPositionRetrieve', - query: { - ballot_item_we_vote_id: ballot_item_we_vote_id, - kind_of_ballot_item: _ballot_store[ballot_item_we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterStarStatusRetrieve: function ( we_vote_id, success ) { - return service.get({ - endpoint: 'voterStarStatusRetrieve', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterStarOnSave: function (we_vote_id, success ) { - return service.get({ - endpoint: 'voterStarOnSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterStarOffSave: function (we_vote_id, success ) { - return service.get({ - endpoint: 'voterStarOffSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterSupportingSave: function (we_vote_id, success ) { - console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); - return service.get({ - endpoint: 'voterSupportingSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterStopSupportingSave: function (we_vote_id, success ) { - console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); - return service.get({ - endpoint: 'voterStopSupportingSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterOpposingSave: function (we_vote_id, success ) { - console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); - return service.get({ - endpoint: 'voterOpposingSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - }, - - voterStopOpposingSave: function (we_vote_id, success ) { - console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); - return service.get({ - endpoint: 'voterStopOpposingSave', - query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success - }); - } }; const BallotStore = createStore({ diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index cd2ed0e6a..335c6856a 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -1,64 +1,23 @@ -import { createStore } from '../utils/createStore'; -import { shallowClone } from '../utils/object-utils'; +import assign from 'object-assign'; import service from '../utils/service'; +import { createStore } from '../utils/createStore'; + +import AppDispatcher from '../dispatcher/AppDispatcher'; +import VoterActions from '../actions/VoterActions'; +import VoterConstants from '../constants/VoterConstants'; -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const assign = require('object-assign'); -const CHANGE_EVENT = 'change'; -const CHANGE_LOCATION = 'change_location'; const cookies = require('../utils/cookies'); -const EventEmitter = require('events').EventEmitter; -const url = require('../config').url; -const VoterActions = require('../actions/VoterActions'); -const VoterConstants = require('../constants/VoterConstants'); +const CHANGE_EVENT = 'change'; -let _location = cookies.getItem('location'); -let _position = {}; let _voter_device_id = cookies.getItem('voter_device_id'); -let _voter_photo_url = ''; -let _voter_ids = []; +let _location = cookies.getItem('location'); let _voter = {}; const VoterAPIWorker = { - generateVoterDeviceId: function ( results ) { - console.log('generating device id...'); - - return service.get({ - endpoint: 'deviceIdGenerate', - results - }); - }, - - createVoter: function ( results ) { - console.log('creating voter id'); - - return service.get({ - endpoint: 'voterCreate', - results - }); - }, - - voterLocationRetrieveFromIP: function ( results ) { - console.log('retrieve location from IP'); - - return service.get({ - endpoint: 'voterLocationRetrieveFromIP', - results - }); - }, - voterRetrieve: function ( results ) { - return service.get({ - endpoint: 'voterRetrieve', - results - }); - } }; const VoterStore = createStore({ - get position() { return _position; }, - get voter_device_id() { return _voter_device_id; }, - get voter_photo_url() { return _voter_photo_url; }, /** * initialize the voter store with data, if no data @@ -78,82 +37,49 @@ const VoterStore = createStore({ return callback(getVoterObject()); else { - - if ( ! _voter_device_id ) { - voterPromiseQueue - .push ( - VoterAPIWorker - .generateVoterDeviceId() - .then ( (response) => { - _voter_device_id = response.voter_device_id; - - cookies.setItem('voter_device_id', _voter_device_id, Infinity); // Set to never expire - }) - ); - - voterPromiseQueue - .push ( - VoterAPIWorker - .createVoter() - ); - } - - if (! _location ) { - voterPromiseQueue - .push ( - VoterAPIWorker - .voterLocationRetrieveFromIP() - .then ( (response) => { - _location = response.voter_location; - - cookies.setItem('location', _location); - }) - ); - } - - if (! _voter_photo_url ) { - - voterPromiseQueue - .push( - VoterAPIWorker - .voterRetrieve() - .then((response) => { - //addVoterToVoterStore(response); - - //_voter_ids.push( response.we_vote_id ); - _voter = assign({}, response); - - // this function polls requests for complete status. - new Promise((resolve) => { - var counted = []; - var count = 0; - - var interval = setInterval(() => { - - var { we_vote_id } = response; - - //_voter = _voter_store [we_vote_id]; - // TODO: Deprecate this? - if ( _voter ) { - _voter_photo_url = _voter.voter_photo_url; - } - - if (counted.indexOf(we_vote_id) < 0) { - count += 1; // TODO Why was this 4? - counted.push(we_vote_id); - } - - if (count === voterPromiseQueue.length && voterPromiseQueue.length !== 0) { - clearInterval(interval); - Promise.all(voterPromiseQueue).then(resolve); - } - - }, 1000); - - }).then(() => callback(getVoterObject())); - }) - ); + if ( ! _voter_device_id ) { + VoterAPIWorker + .deviceIdGenerate( (res, a) => { + console.log(res, a); + debugger; + }) + .then ( (response) => { + debugger; + _voter_device_id = response.voter_device_id; + + cookies.setItem('voter_device_id', _voter_device_id, Infinity); // Set to never expire + }) + .catch( (err) => { + debugger; + console.log(err); + }) } + // + // VoterAPIWorker + // .createVoter() + // } + // + // if (! _location ) { + // VoterAPIWorker + // .voterLocationRetrieveFromIP() + // .then ( (response) => { + // _location = response.voter_location; + // + // cookies.setItem('location', _location); + // }) + // } + // + // if (! _voter_photo_url ) { + // + // VoterAPIWorker + // .voterRetrieve() + // .then((response) => { + // //addVoterToVoterStore(response); + // + // //_voter_ids.push( response.we_vote_id ); + // _voter = assign({}, response); + // }) + // } } }, @@ -209,88 +135,18 @@ const VoterStore = createStore({ */ getLocation: function () { return _location; - }, - - voterRetrieveFresh: function (callback) { - console.log('voterRetrieveFresh '); - var voterPromiseQueue = []; - var getVoterObject = this.getVoterObject.bind(this); - - if (!callback || typeof callback !== 'function') - throw new Error('VoterStore: voterRetrieveFresh must be called with callback'); - - voterPromiseQueue - .push( - VoterAPIWorker - .voterRetrieve() - .then((response) => { - //addVoterToVoterStore(response); - // - //_voter_ids.push( response.we_vote_id ); - _voter = assign({}, response); - - // this function polls requests for complete status. - new Promise((resolve) => { - var counted = []; - var count = 0; - - var interval = setInterval(() => { - - var { we_vote_id } = response; - - if (counted.indexOf(we_vote_id) < 0) { - count += 1; - counted.push(we_vote_id); - } - - if (count === voterPromiseQueue.length && voterPromiseQueue.length !== 0) { - clearInterval(interval); - Promise.all(voterPromiseQueue).then(resolve); - } - - }, 1000); - - }).then(() => callback(getVoterObject())); - }) - ); - }, - - /** - */ - emitChange: function () { - this.emit(CHANGE_EVENT); - }, - - /** - * @param {Function} callback subscribe to changes - */ - _addChangeListener: function (callback) { - this.on(CHANGE_EVENT, callback); - }, - - /** - * @param {Function} callback unsubscribe to changes - */ - _removeChangeListener: function (callback) { - this.removeListener(CHANGE_EVENT, callback); } }); AppDispatcher.register( action => { switch (action.actionType) { - case VoterConstants.VOTER_LOCATION_RETRIEVE: // ChangeLocation + case VoterConstants.VOTER_CHANGE_LOCATION: // ChangeLocation VoterAPIWorker .voterLocationRetrieveFromIP( () => VoterStore.emitChange() ); break; - case VoterConstants.VOTER_RETRIEVE: // voterRetrieve - VoterAPIWorker - .voterRetrieve( - () => VoterStore.emitChange() - ); - break; default: break; } diff --git a/src/js/utils/service.js b/src/js/utils/service.js index c98420c36..acf7507ae 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -16,11 +16,12 @@ const web_app_config = require('../config'); const defaults = { dataType: 'json', WE_VOTE_SERVER_API_ROOT_URL: web_app_config.WE_VOTE_SERVER_API_ROOT_URL, + query: {} }; const service = {}; -service.get = function (options) { +function get (options) { var opts = assign(defaults, options); opts.WE_VOTE_SERVER_API_ROOT_URL = url.resolve(opts.WE_VOTE_SERVER_API_ROOT_URL, opts.endpoint); @@ -50,4 +51,123 @@ service.get = function (options) { ); }; -export default service; +export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { + return get({ + endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', + query: { text_for_map_search }, success + }); +} + +export function candidatesRetrieve (office_we_vote_id, success ) { + return get({ + endpoint: 'candidatesRetrieve', + query: { office_we_vote_id }, + success + }); +} + +// get the ballot items +export function voterBallotItemsRetrieve (success) { + return get({ endpoint: 'voterBallotItemsRetrieve', success }); +} + +export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'positionOpposeCountForBallotItem', + query: { id, kind_of_ballot_item }, success + }); +} + +// get measure support an opposition +export function positionSupportCountForBallotItem (id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'positionSupportCountForBallotItem', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterPositionRetrieve (ballot_item_we_vote_id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'voterPositionRetrieve', + query: { ballot_item_we_vote_id, kind_of_ballot_item }, success + }); +} + +export function voterStarStatusRetrieve (id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'voterStarStatusRetrieve', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStarOnSave (id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'voterStarOnSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStarOffSave (id, kind_of_ballot_item, success ) { + return get({ + endpoint: 'voterStarOffSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterSupportingSave (id, kind_of_ballot_item, success ) { + console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); + + return get({ + endpoint: 'voterSupportingSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStopSupportingSave (id, kind_of_ballot_item, success ) { + console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); + + return get({ + endpoint: 'voterStopSupportingSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterOpposingSave (id, kind_of_ballot_item, success ) { + console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); + + return get({ + endpoint: 'voterOpposingSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStopOpposingSave (id, kind_of_ballot_item, success ) { + console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); + + return get({ + endpoint: 'voterStopOpposingSave', + query: { id, kind_of_ballot_item }, success + }); +} + +export function deviceIdGenerate (success) { + console.log('generating device id...'); + + return get({ endpoint: 'deviceIdGenerate', success }); +} + +export function createVoter (success) { + console.log('creating voter id'); + + return get({ endpoint: 'voterCreate', success }); +} + +export function voterLocationRetrieveFromIP (success) { + console.log('retrieve location from IP'); + + return get({ endpoint: 'voterLocationRetrieveFromIP', success }); +} + +export function voterRetrieve (success) { + return get({ endpoint: 'voterRetrieve', success }); +} From ec6ec9d0481c360ce2fd3ab550e902606e0c51ec Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Sun, 14 Feb 2016 11:51:58 -0500 Subject: [PATCH 18/92] updates to voterstore saveaddress and some other service changes to the app --- src/js/Application.jsx | 16 ++-- src/js/components/Header.jsx | 10 ++- src/js/index.js | 13 +-- src/js/routes/Settings/Location.jsx | 80 ++++++++++++----- src/js/stores/BallotStore.js | 131 +++++++++++++++++++++++++++- src/js/stores/VoterStore.js | 113 ++++++++++-------------- src/js/utils/service.js | 9 +- 7 files changed, 258 insertions(+), 114 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index 7ef6d23e3..c6196401c 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -8,26 +8,26 @@ import VoterStore from './stores/VoterStore'; export default class Application extends Component { static propTypes = { children: PropTypes.object, - voter_object: PropTypes.object + voter: PropTypes.object }; constructor(props) { super(props); this.state = { - voter_object: {} + voter: {} }; } componentDidMount() { console.log("Application: About to initialize VoterStore"); - VoterStore.initialize((voter_object) => { - //console.log(voter_object, 'voter_object is your object') - this.setState({voter_object}); + VoterStore.signInStatus((voter) => { + //console.log(voter, 'voter is your object') + this.setState({voter}); }); } render() { - var { voter_object } = this.state; + var { voter } = this.state; return (
@@ -45,9 +45,9 @@ export default class Application extends Component {
{ - voter_object ? + voter ?
- +
: diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 2ebdc1659..30e90617c 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -2,12 +2,20 @@ import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import Headroom from "react-headroom"; +import VoterStore from '../stores/VoterStore'; + export default class Header extends Component { constructor(props) { super(props); + this.state = {}; + } + + componentDidMount() { + VoterStore.getLocation( location => this.setState({ location })) } render () { + var {location} = this.state; return (
@@ -20,7 +28,7 @@ export default class Header extends Component { diff --git a/src/js/index.js b/src/js/index.js index 32c64cd60..54a6c72e9 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom'; import { createHistory } from 'history'; import Root from './Root'; +import { voterBallotItemsRetrieveFromGoogleCivic } from './utils/service'; import VoterStore from './stores/VoterStore'; console.log('Entering WebApp/src/js/index.js'); @@ -10,7 +11,12 @@ console.log('Entering WebApp/src/js/index.js'); // polyfill if (!Object.assign) Object.assign = React.__spread; -const firstVisit = VoterStore.voter_device_id ? false : true; +VoterStore.getDeviceId( (firstVisit, id) => + ReactDOM.render( + , + document.getElementById('app') + ) +); //console.log("index.js: About to initialize VoterStore"); //VoterStore.initialize((voter_object) => { @@ -20,7 +26,4 @@ const firstVisit = VoterStore.voter_device_id ? false : true; // ); //}); -ReactDOM.render( - , - document.getElementById('app') -); + diff --git a/src/js/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index ab89ee07a..3d2a58518 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -1,30 +1,62 @@ import React, { Component } from 'react'; import { Button, ButtonToolbar } from 'react-bootstrap'; import HeaderBackNavigation from '../../components/Navigation/HeaderBackNavigation'; +import VoterStore from '../../stores/VoterStore'; export default class Location extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
-
-

Change Location

-
- Please enter the address (or just the city) where you registered to - vote. The more location information you can provide, the more ballot information will - be visible. - - - - - - -
-
-
- ); - } + constructor(props) { + super(props); + this.state = {} + } + + componentDidMount() { + VoterStore.getLocation( location => this.setState({ location })) + } + + updateLocation (e) { + this.setState({ + location: e.target.value + }); + } + + saveLocation (e) { + VoterStore.saveLocation ( this.state.location ); + location.href="/ballot" + } + + render() { + var { location } = this.state; + + return ( +
+
+

+ Change Location +

+
+ + Please enter the address (or just the city) where you registered to + vote. The more location information you can provide, the more ballot information will + be visible. + + + + + + + + +
+
+
+ ); + } } diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 041acad41..0eda655bb 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -1,4 +1,4 @@ -import service from '../utils/service'; +import { get } from '../utils/service'; import { createStore } from '../utils/createStore'; import { shallowClone } from '../utils/object-utils'; @@ -25,6 +25,133 @@ function ballotItemIsMeasure (we_vote_id) { } const BallotAPIWorker = { + voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { + return get({ + endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', + query: { text_for_map_search }, success + }); + }, + + candidatesRetrieve: function (office_we_vote_id, success ) { + return get({ + endpoint: 'candidatesRetrieve', + query: { office_we_vote_id }, + success + }); + }, + + // get the ballot items + voterBallotItemsRetrieve: function ( success ) { + return get({ + endpoint: 'voterBallotItemsRetrieve', + success + }); + }, + + positionOpposeCountForBallotItem: function (we_vote_id, success ) { + return get({ + endpoint: 'positionOpposeCountForBallotItem', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + // get measure support an opposition + positionSupportCountForBallotItem: function (we_vote_id, success ) { + return get({ + endpoint: 'positionSupportCountForBallotItem', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterPositionRetrieve: function ( ballot_item_we_vote_id, success ) { + return get({ + endpoint: 'voterPositionRetrieve', + query: { + ballot_item_we_vote_id: ballot_item_we_vote_id, + kind_of_ballot_item: _ballot_store[ballot_item_we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterStarStatusRetrieve: function ( we_vote_id, success ) { + return get({ + endpoint: 'voterStarStatusRetrieve', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterStarOnSave: function (we_vote_id, success ) { + return get({ + endpoint: 'voterStarOnSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterStarOffSave: function (we_vote_id, success ) { + return get({ + endpoint: 'voterStarOffSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterSupportingSave: function (we_vote_id, success ) { + console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); + return get({ + endpoint: 'voterSupportingSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterStopSupportingSave: function (we_vote_id, success ) { + console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); + return get({ + endpoint: 'voterStopSupportingSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterOpposingSave: function (we_vote_id, success ) { + console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); + return get({ + endpoint: 'voterOpposingSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + }, + + voterStopOpposingSave: function (we_vote_id, success ) { + console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); + return get({ + endpoint: 'voterStopOpposingSave', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, success + }); + } }; const BallotStore = createStore({ @@ -98,7 +225,6 @@ const BallotStore = createStore({ BallotAPIWorker .candidatesRetrieve ( we_vote_id ) .then( (response) => { - var office_display_name = _ballot_store[response.office_we_vote_id]['ballot_item_display_name']; var cand_list = _ballot_store [ response.office_we_vote_id ] . candidate_list = []; @@ -109,7 +235,6 @@ const BallotStore = createStore({ var { we_vote_id: candidate_we_vote_id } = candidate; cand_list . push (candidate_we_vote_id); _ballot_store [ candidate_we_vote_id ] = shallowClone( candidate ); - _ballot_store [ candidate_we_vote_id ].office_display_name = office_display_name; promiseQueue .push ( diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 335c6856a..180a584d3 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -1,5 +1,5 @@ import assign from 'object-assign'; -import service from '../utils/service'; +import { deviceIdGenerate, voterLocationRetrieveFromIP } from '../utils/service'; import { createStore } from '../utils/createStore'; import AppDispatcher from '../dispatcher/AppDispatcher'; @@ -13,9 +13,19 @@ let _voter_device_id = cookies.getItem('voter_device_id'); let _location = cookies.getItem('location'); let _voter = {}; -const VoterAPIWorker = { +function error (err) { + console.error('WVError:', err.message); +} -}; +function setVoterDeviceId (id) { + _voter_device_id = id; + cookies.setItem('voter_device_id', id, Infinity) +} + +function setVoterLocation (location) { + _location = location; + cookies.setItem('location', location, Infinity); +} const VoterStore = createStore({ @@ -24,63 +34,23 @@ const VoterStore = createStore({ * and callback with the voter items * @return {Boolean} */ - initialize: function (callback) { - console.log("VoterStore.initialize"); - var voterPromiseQueue = []; - var getVoterObject = this.getVoterObject.bind(this); - + getDeviceId: function (callback) { if (!callback || typeof callback !== 'function') - throw new Error('VoterStore: initialize must be called with callback'); - - // Do we have the Voter data stored in the browser? - if (Object.keys(_voter).length) - return callback(getVoterObject()); - - else { - if ( ! _voter_device_id ) { - VoterAPIWorker - .deviceIdGenerate( (res, a) => { - console.log(res, a); - debugger; - }) - .then ( (response) => { - debugger; - _voter_device_id = response.voter_device_id; - - cookies.setItem('voter_device_id', _voter_device_id, Infinity); // Set to never expire - }) - .catch( (err) => { - debugger; - console.log(err); - }) - } - // - // VoterAPIWorker - // .createVoter() - // } - // - // if (! _location ) { - // VoterAPIWorker - // .voterLocationRetrieveFromIP() - // .then ( (response) => { - // _location = response.voter_location; - // - // cookies.setItem('location', _location); - // }) - // } - // - // if (! _voter_photo_url ) { - // - // VoterAPIWorker - // .voterRetrieve() - // .then((response) => { - // //addVoterToVoterStore(response); - // - // //_voter_ids.push( response.we_vote_id ); - // _voter = assign({}, response); - // }) - // } - } + throw new Error('VoterStore: getDeviceId must be called with callback'); + + if ( !_voter_device_id ) deviceIdGenerate() + .then ( res => { + var {voter_device_id: id} = res; + setVoterDeviceId(id); + callback(true, id); + }) + .catch( error ); + + else callback( false, _voter_device_id ); + }, + + signInStatus: function (callback) { + callback(assign({}, _voter)) }, getVoterObject: function () { @@ -101,9 +71,10 @@ const VoterStore = createStore({ return _voter.signed_in_personal; }, - getVoterPhotoURL: function () { + getVoterPhotoURL: function (callback) { console.log("VoterStore getVoterPhotoURL"); - return _voter_photo_url; + if ( _voter_photo_url ) callback(_voter_photo_url); + else callback(new Error('missing voter photo url')); }, /** @@ -129,12 +100,23 @@ const VoterStore = createStore({ return location; }, + saveLocation: function (location) { + cookies.setItem('location', location); + VoterActions.ChangeLocation(location); + }, + /** * get the Voters location * @return {String} location */ - getLocation: function () { - return _location; + getLocation: function (callback) { + if (_location) callback(_location) + else voterLocationRetrieveFromIP() + .then( response => { + var { voter_location: location } = response; + setVoterLocation(location); + callback(location); + }); } }); @@ -142,10 +124,7 @@ AppDispatcher.register( action => { switch (action.actionType) { case VoterConstants.VOTER_CHANGE_LOCATION: // ChangeLocation - VoterAPIWorker - .voterLocationRetrieveFromIP( - () => VoterStore.emitChange() - ); + VoterStore.emitChange(); break; default: break; diff --git a/src/js/utils/service.js b/src/js/utils/service.js index acf7507ae..9db83fb2f 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -19,9 +19,7 @@ const defaults = { query: {} }; -const service = {}; - -function get (options) { +export function get (options) { var opts = assign(defaults, options); opts.WE_VOTE_SERVER_API_ROOT_URL = url.resolve(opts.WE_VOTE_SERVER_API_ROOT_URL, opts.endpoint); @@ -49,7 +47,7 @@ function get (options) { } }) ); -}; +} export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { return get({ @@ -61,8 +59,7 @@ export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, su export function candidatesRetrieve (office_we_vote_id, success ) { return get({ endpoint: 'candidatesRetrieve', - query: { office_we_vote_id }, - success + query: { office_we_vote_id }, success }); } From 1bb0e66c7ce7af726b826c2ebea407f138006e63 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Mon, 15 Feb 2016 10:35:49 -0800 Subject: [PATCH 19/92] Fixed some broken links. Updated some styles on the new Candidate route page. --- src/js/Root.jsx | 4 ++-- .../Navigation/BottomContinueNavigation.jsx | 2 ++ .../routes/{AddFriend.jsx => AddFriends.jsx} | 5 +++-- src/js/routes/Ballot/Candidate.jsx | 22 ++++++++++--------- src/js/routes/Connect.jsx | 2 +- src/sass/base/_fonts.scss | 16 ++++++++++++++ src/sass/components/_candidate.scss | 4 ++++ 7 files changed, 40 insertions(+), 15 deletions(-) rename src/js/routes/{AddFriend.jsx => AddFriends.jsx} (89%) diff --git a/src/js/Root.jsx b/src/js/Root.jsx index 11dc5c60a..d7b7d0b83 100644 --- a/src/js/Root.jsx +++ b/src/js/Root.jsx @@ -38,7 +38,7 @@ import Requests from './routes/Requests'; import Connect from './routes/Connect'; import Activity from './routes/Activity'; import NotFound from './routes/NotFound'; -import AddFriend from './routes/AddFriend'; +import AddFriends from './routes/AddFriends'; class Root extends Component { @@ -80,7 +80,7 @@ class Root extends Component { - + diff --git a/src/js/components/Navigation/BottomContinueNavigation.jsx b/src/js/components/Navigation/BottomContinueNavigation.jsx index 6e9917a47..de1ecd603 100644 --- a/src/js/components/Navigation/BottomContinueNavigation.jsx +++ b/src/js/components/Navigation/BottomContinueNavigation.jsx @@ -1,6 +1,8 @@ "use strict"; import React, { Component, PropTypes } from 'react'; +import { Button } from 'react-bootstrap'; +import { Link } from 'react-router'; class BottomContinueNavigation extends Component { static propTypes = { diff --git a/src/js/routes/AddFriend.jsx b/src/js/routes/AddFriends.jsx similarity index 89% rename from src/js/routes/AddFriend.jsx rename to src/js/routes/AddFriends.jsx index f6bcc8490..97fb0656f 100644 --- a/src/js/routes/AddFriend.jsx +++ b/src/js/routes/AddFriends.jsx @@ -1,10 +1,11 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; import { Input } from 'react-bootstrap'; - import BottomContinueNavigation from '../components/Navigation/BottomContinueNavigation'; -export default class AddFriend extends Component { +{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479679 */} + +export default class AddFriends extends Component { constructor(props) { super(props); } diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 09392cf9f..ff014d03e 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -41,6 +41,7 @@ export default class Candidate extends Component { } return ( +
{/*
@@ -97,13 +98,15 @@ export default class Candidate extends Component {
-
{ candidate.office_display_name }
+
Running for { candidate.office_display_name }
+ {/* Post privately box */} + {/*
  • @@ -112,12 +115,15 @@ export default class Candidate extends Component {
+ */}
    + + {/* One organization's Position on this Candidate */}
  • - +
    @@ -131,22 +137,18 @@ export default class Candidate extends Component {
    Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum.
    + {/* Likes coming in a later version
    23 Likes
    -
  • -
  • -  Another Organization
    {/* TODO icon-org-placeholder */} - opposes Yesterday at 2:34 PM
    - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
    - 5 Likes
    + */}
+ ); } diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 988f4eeff..55d746f33 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -29,7 +29,7 @@ export default class Connect extends Component {

Add Friends

- +

Friends can see what you support and oppose. We never sell emails.

diff --git a/src/sass/base/_fonts.scss b/src/sass/base/_fonts.scss index a4ba3be5e..b5ec93dc8 100644 --- a/src/sass/base/_fonts.scss +++ b/src/sass/base/_fonts.scss @@ -87,3 +87,19 @@ .icon-main { color: #23527C; } + +.icon-org-resting-color { + color: #A9A9A9; +} + +.icon-org-lg { + font-size: 5em; +} + +.icon-org-medium { + font-size: 2em; +} + +.icon-org-small { + font-size: 15px; +} diff --git a/src/sass/components/_candidate.scss b/src/sass/components/_candidate.scss index b0f3686bc..7162cfbe2 100644 --- a/src/sass/components/_candidate.scss +++ b/src/sass/components/_candidate.scss @@ -26,4 +26,8 @@ $largeFont: 2em; .support { padding-right: 1em; } + .running-for-office-emphasis { + font-size: 1.3em; + font-weight: 500; + } } From 2024b583330345980a4bfe38b28c65688cf9fbea Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 15 Feb 2016 15:07:44 -0500 Subject: [PATCH 20/92] Update candidate view UI --- src/js/routes/Ballot/Candidate.jsx | 21 +++++++++++++-------- src/sass/base/_base.scss | 4 ++++ src/sass/components/_itemActionbar.scss | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index ff014d03e..a26f9fe38 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -12,7 +12,10 @@ import StarAction from '../../components/StarAction'; export default class Candidate extends Component { static propTypes = { //history: PropTypes.func.isRequired, + history: PropTypes.string, + oppose_on: PropTypes.boolean, params: PropTypes.object.isRequired, + support_on: PropTypes.boolean }; constructor(props) { @@ -41,8 +44,8 @@ export default class Candidate extends Component { } return ( -
-
+
+
{/*
@@ -97,14 +100,16 @@ export default class Candidate extends Component { Courtesy of Ballotpedia.org */}
-
-
Running for { candidate.office_display_name }
- +
+
+
Running for { candidate.office_display_name }
+
+
-
+
{/* Post privately box */} {/*
    diff --git a/src/sass/base/_base.scss b/src/sass/base/_base.scss index 04f69adf2..7c1adc5d1 100644 --- a/src/sass/base/_base.scss +++ b/src/sass/base/_base.scss @@ -7,6 +7,10 @@ html { box-sizing: border-box; } +#app { + height: 100%; +} + *, *::before, *::after { box-sizing: inherit; } diff --git a/src/sass/components/_itemActionbar.scss b/src/sass/components/_itemActionbar.scss index 7a0c2a22a..c17530878 100644 --- a/src/sass/components/_itemActionbar.scss +++ b/src/sass/components/_itemActionbar.scss @@ -1,4 +1,4 @@ -.item-actionbar { +.item-actionbar, .item-actionbar2 { border-top: 1px solid #DDD; padding:10px 5px; From a088d99edfd29fa8dfb430e5cb8effd96ed2b8d1 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 15 Feb 2016 20:56:11 -0500 Subject: [PATCH 21/92] #3 - Ballot candidate page closes #3 --- src/js/routes/Ballot/Candidate.jsx | 27 +++++++++------------------ src/sass/utils/_app.scss | 4 ++++ 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index a26f9fe38..1390c43f6 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -70,7 +70,7 @@ export default class Candidate extends Component { is_starred={candidate.is_starred} />
    { @@ -86,7 +86,7 @@ export default class Candidate extends Component { }
    -
    +

    -
    - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
    - Courtesy of Ballotpedia.org */} -
    +

    Running for { candidate.office_display_name }

    -
    -
    -
    Running for { candidate.office_display_name }
    -
    -
    -
    +
    {/* Post privately box */} {/* @@ -126,12 +117,12 @@ export default class Candidate extends Component { {/* One organization's Position on this Candidate */}
  • -
    - +
    +
    -
    +

    Organization Name
    {/* TODO icon-org-placeholder */} diff --git a/src/sass/utils/_app.scss b/src/sass/utils/_app.scss index c8edeaaa0..c052899ac 100644 --- a/src/sass/utils/_app.scss +++ b/src/sass/utils/_app.scss @@ -5,3 +5,7 @@ } } } + +.transparent, .transparent:hover { + color: transparent; +} From 95050cea91df33c0b8dfccc26f11662da2463047 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Fri, 12 Feb 2016 17:32:12 -0800 Subject: [PATCH 22/92] Add office display name to candidate page --- src/js/routes/Ballot/Candidate.jsx | 2 +- src/js/stores/BallotStore.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index d404adc77..09392cf9f 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -97,7 +97,7 @@ export default class Candidate extends Component {

    -
    Running for US House - District 12
    +
    { candidate.office_display_name }
    diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 871b2879f..e275272a8 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -225,6 +225,7 @@ const BallotStore = createStore({ BallotAPIWorker .candidatesRetrieve ( we_vote_id ) .then( (response) => { + var office_display_name = _ballot_store[response.office_we_vote_id]['ballot_item_display_name']; var cand_list = _ballot_store [ response.office_we_vote_id ] . candidate_list = []; @@ -235,6 +236,7 @@ const BallotStore = createStore({ var { we_vote_id: candidate_we_vote_id } = candidate; cand_list . push (candidate_we_vote_id); _ballot_store [ candidate_we_vote_id ] = shallowClone( candidate ); + _ballot_store [ candidate_we_vote_id ].office_display_name = office_display_name; promiseQueue .push ( From be103c630da4d973385bcda3c7c295ab0908ef95 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 13 Feb 2016 11:00:38 -0500 Subject: [PATCH 23/92] added semver to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a159746f0..5f58d0d8a 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,8 @@ You should be able to visit WebApp here: ## After Installation: Working with WebApp Day-to-Day [Read about working with WebApp on a daily basis](README_WORKING_WITH_WEB_APP.md) + +## SemVer + +We follow [SemVer](http://semver.org/) for our releases. Please read if you plan +to tag for any releases. From 9d6d52091332b8dea8a98e5ef872765ed0f705fd Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Sat, 13 Feb 2016 19:02:11 -0800 Subject: [PATCH 24/92] Organization name and support shows on candidate detail --- src/js/actions/BallotActions.js | 22 ++ src/js/components/Ballot/OrganizationItem.jsx | 60 ++++++ src/js/components/StarAction.jsx | 1 + src/js/constants/BallotConstants.js | 4 +- src/js/routes/Ballot/Ballot.jsx | 1 + src/js/routes/Ballot/Candidate.jsx | 204 +++++++++--------- src/js/stores/BallotStore.js | 48 ++++- 7 files changed, 234 insertions(+), 106 deletions(-) create mode 100644 src/js/components/Ballot/OrganizationItem.jsx diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index d93b6a450..62a841328 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -5,6 +5,28 @@ var BallotConstants = require('../constants/BallotConstants'); // In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ VOTER_SUPPORTING_SAVE) // When action calls one of these functions, we are telling the code in the AppDispatcher block to run module.exports = { + + downloadOrganizations: function( we_vote_id) { + AppDispatcher.dispatch({ + actionType: BallotConstants.DOWNLOAD_ORGANIZATIONS, + we_vote_id + }); + }, + + voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE + AppDispatcher.dispatch({ + actionType: BallotConstants.VOTER_SUPPORTING_SAVE, + we_vote_id + }); + }, + + voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE + AppDispatcher.dispatch({ + actionType: BallotConstants.VOTER_SUPPORTING_SAVE, + we_vote_id + }); + }, + voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE AppDispatcher.dispatch({ actionType: BallotConstants.VOTER_SUPPORTING_SAVE, diff --git a/src/js/components/Ballot/OrganizationItem.jsx b/src/js/components/Ballot/OrganizationItem.jsx new file mode 100644 index 000000000..4ae3d20e6 --- /dev/null +++ b/src/js/components/Ballot/OrganizationItem.jsx @@ -0,0 +1,60 @@ +import React, { Component, PropTypes } from 'react'; +import { Link } from 'react-router'; +import BallotActions from '../../actions/BallotActions'; +import BallotStore from '../../stores/BallotStore'; + +export default class OrganizationItem extends Component { + static propTypes = { + position_we_vote_id: PropTypes.string.isRequired, + }; + + constructor (props) { + super(props); + this.state = { organization: this.props}; + } + + componentDidMount () { + BallotStore.addChangeListener(this._onChange.bind(this)); + } + + componentWillUnmount () { + BallotStore.removeChangeListener(this._onChange.bind(this)); + } + + _onChange () { + // this.setState({ organization: BallotStore.getOrganization(this.props.candidate_we_vote_id, this.props.item.position_we_vote_id) }); + } + + render() { + var organization = this.state.organization; + var supportText = organization.is_oppose ? "Opposes" : "Supports"; + return ( +
    +
  • +
    +
    + + + +
    +
    +

    + + { organization.speaker_label } + +

    +

    {supportText} Yesterday at 7:18 PM

    +
    +
    +
    + Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) +
    +
    + 23 Likes
    +
  • +
    + ); + } +} diff --git a/src/js/components/StarAction.jsx b/src/js/components/StarAction.jsx index ed8bf03cd..f4283a533 100644 --- a/src/js/components/StarAction.jsx +++ b/src/js/components/StarAction.jsx @@ -40,6 +40,7 @@ export default class StarAction extends Component { } _onChange () { + console.log("StarActiononChange"); this.setState({ is_starred: BallotStore.getStarState(this.props.we_vote_id) }); diff --git a/src/js/constants/BallotConstants.js b/src/js/constants/BallotConstants.js index 95bbe1707..ba7f6c997 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -5,5 +5,7 @@ module.exports = require('keymirror')({ VOTER_OPPOSING_SAVE: null, VOTER_STOP_OPPOSING_SAVE: null, VOTER_STAR_ON_SAVE: null, - VOTER_STAR_OFF_SAVE: null + VOTER_STAR_OFF_SAVE: null, + VOTER_STAR_OFF_SAVE: null, + DOWNLOAD_ORGANIZATIONS: null }); diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 58bdad8f0..249a838b6 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -3,6 +3,7 @@ import { Link } from 'react-router'; import BallotStore from '../../stores/BallotStore'; import BallotItem from '../../components/Ballot/BallotItem'; +import BallotActions from '../../actions/BallotActions'; export default class Ballot extends Component { static propTypes = { diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 09392cf9f..b9402ca23 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -5,26 +5,50 @@ import { Link } from 'react-router'; import BallotActions from '../../actions/BallotActions'; import BallotStore from '../../stores/BallotStore'; import CandidateDetail from '../../components/Ballot/CandidateDetail'; +import OrganizationItem from '../../components/Ballot/OrganizationItem'; import ItemActionbar from '../../components/ItemActionbar'; import ItemActionBar2 from '../../components/ItemActionBar2'; import StarAction from '../../components/StarAction'; export default class Candidate extends Component { static propTypes = { - //history: PropTypes.func.isRequired, - params: PropTypes.object.isRequired, + params: PropTypes.object.isRequired }; constructor(props) { super(props); + this.state = { candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }; + } + // no candidate exists... go to ballot + componentDidMount(){ + if (Object.keys(this.state.candidate).length === 0){ + this.props.history.replace('/ballot'); + } + console.log('Candidate Component Mounted!') + BallotActions.downloadOrganizations(this.props.params.we_vote_id); + BallotStore.addChangeListener(this._onChange.bind(this)); + } + + componentWillUnmount(){ + BallotStore.removeChangeListener(this._onChange.bind(this)); + } + + _onChange(){ + console.log('onChange!'); + var we_vote_id = this.props.params.we_vote_id; + this.setState({ candidate: BallotStore.getCandidateByWeVoteId(we_vote_id) }); } render() { var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); + if (!candidate){return (
    );} + // var candidate = this.state.candidate; + console.log("Candidate Object:"); + console.log(candidate); + var position_list = candidate ? candidate.position_list : undefined; - // no candidate exists... go to ballot - if (Object.keys(candidate).length === 0) - this.props.history.replace('/ballot'); + if (Object.keys(candidate).length === 0){ + this.props.history.replace('/ballot');} var support_item; if (this.props.support_on) { @@ -41,113 +65,87 @@ export default class Candidate extends Component { } return ( -
    - {/* -
    -
    - - < Back to My Ballot - -
    -
    - - - - More Opinions - -
    -
    - */} - - -
    -
    - - { - candidate.candidate_photo_url ? - - candidate-photo : +
    + {/* +
    +
    + + < Back to My Ballot + +
    +
    + + + + More Opinions + +
    +
    + */} - + +
    +
    - } -
    -
    -

    - - { candidate.ballot_item_display_name } - -

    -
    - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
    - Courtesy of Ballotpedia.org */} -
    -
    -
    -
    { candidate.office_display_name }
    - + { + candidate.candidate_photo_url ? + candidate-photo : + + } +
    +
    +

    + + { candidate.ballot_item_display_name } + +

    +
    + {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
    + Courtesy of Ballotpedia.org */}
    -
    -
      -
    • -
      - - -
      -
    • -
    -
      +
      +
      { candidate.office_display_name }
      + +
      +
    +
    +
    • -
      -
      - - - -
      -
      -

      - - Organization Name
      {/* TODO icon-org-placeholder */} - -

      -

      supports Yesterday at 7:18 PM

      -
      +
      + +
      -
      - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) -
      -
      - 23 Likes
    • -
    • -  Another Organization
      {/* TODO icon-org-placeholder */} - opposes Yesterday at 2:34 PM
      - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
      - 5 Likes
      -
    • -
    -
    +
+
    + { (position_list) ? position_list.map( item => + + ) : (
    ) + } +
- ); + +
+ ); } } diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index e275272a8..50f31b26c 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -5,12 +5,12 @@ import { shallowClone } from '../utils/object-utils'; const AppDispatcher = require('../dispatcher/AppDispatcher'); const BallotConstants = require('../constants/BallotConstants'); - let _ballot_store = {}; let _ballot_order_ids = []; let _google_civic_election_id = null; const MEASURE = 'MEASURE'; +var BALLOT_CHANGE_EVENT = 'BALLOT_CHANGE_EVENT'; function addItemsToBallotStore (ballot_item_list) { ballot_item_list.forEach( ballot_item => { @@ -48,6 +48,19 @@ const BallotAPIWorker = { }); }, + positionListForBallotItem : function( we_vote_id, success_func) { + return service.get({ + endpoint: 'positionListForBallotItem', + query: { + ballot_item_id: _ballot_store[we_vote_id].id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + }, + success: function(res){ + success_func(res); + } + }); + }, + positionOpposeCountForBallotItem: function (we_vote_id, success ) { return service.get({ endpoint: 'positionOpposeCountForBallotItem', @@ -346,6 +359,32 @@ const BallotStore = createStore({ return temp; }, + // emitChange: function () { + // this.emit(BALLOT_CHANGE_EVENT); + // }, + // + // addChangeListener: function(callback) { + // console.log("Change listener added"); + // this.on(BALLOT_CHANGE_EVENT, callback); + // }, + // + // removeChangeListener: function(callback) { + // console.log("Change listener removed!"); + // this.removeListener(BALLOT_CHANGE_EVENT, callback); + // }, + + getCandidatePositionsByWeVoteId: function(candidate_we_vote_id){ + BallotAPIWorker.positionListForBallotItem(candidate_we_vote_id, function(res){ + var we_vote_id = candidate_we_vote_id; + _ballot_store[we_vote_id].position_list = res.position_list; + BallotStore.emitChange(); + }.bind(this)) + }, + + getOrganization: function(candidate_we_vote_id, organization_we_vote_id){ + return _ballot_store[candidate_we_vote_id].position_list.organization_we_vote_id; + }, + /** * get the number of orgs and friends that the Voter follows who support this ballot item * @param {String} we_vote_id ballot items we_vote_id @@ -505,8 +544,12 @@ function setLocalOpposeOffState(we_vote_id) { AppDispatcher.register( action => { var { we_vote_id } = action; + switch (action.actionType) { + + case BallotConstants.DOWNLOAD_ORGANIZATIONS: + BallotStore.getCandidatePositionsByWeVoteId(we_vote_id); + break; - switch (action.actionType) { case BallotConstants.VOTER_SUPPORTING_SAVE: BallotAPIWorker.voterSupportingSave( we_vote_id, () => setLocalSupportOnState(we_vote_id) @@ -514,6 +557,7 @@ AppDispatcher.register( action => { && BallotStore.emitChange() ); break; + case BallotConstants.VOTER_STOP_SUPPORTING_SAVE: BallotAPIWorker.voterStopSupportingSave( we_vote_id, () => setLocalSupportOffState(we_vote_id) From 03d48d1529c2bb87357addccd305ef17bc0e7a6e Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 15 Feb 2016 18:16:49 -0800 Subject: [PATCH 25/92] Create Position Store action and constants --- src/js/actions/BallotActions.js | 7 +- src/js/actions/PositionActions.js | 17 ++++ ...{OrganizationItem.jsx => PositionItem.jsx} | 31 ++++--- src/js/components/Ballot/PositionList.jsx | 37 +++++++++ src/js/constants/BallotConstants.js | 3 +- src/js/constants/PositionConstants.js | 3 + src/js/routes/Ballot/Candidate.jsx | 51 ++++-------- src/js/stores/BallotStore.js | 56 ++++++------- src/js/stores/PositionStore.js | 81 +++++++++++++++++++ 9 files changed, 204 insertions(+), 82 deletions(-) create mode 100644 src/js/actions/PositionActions.js rename src/js/components/Ballot/{OrganizationItem.jsx => PositionItem.jsx} (55%) create mode 100644 src/js/components/Ballot/PositionList.jsx create mode 100644 src/js/constants/PositionConstants.js create mode 100644 src/js/stores/PositionStore.js diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index 62a841328..548293d74 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -6,10 +6,11 @@ var BallotConstants = require('../constants/BallotConstants'); // When action calls one of these functions, we are telling the code in the AppDispatcher block to run module.exports = { - downloadOrganizations: function( we_vote_id) { + positionsRetrieved: function( we_vote_id, payload) { AppDispatcher.dispatch({ - actionType: BallotConstants.DOWNLOAD_ORGANIZATIONS, - we_vote_id + actionType: BallotConstants.POSITIONS_RETRIEVED, + payload: payload, + we_vote_id: we_vote_id }); }, diff --git a/src/js/actions/PositionActions.js b/src/js/actions/PositionActions.js new file mode 100644 index 000000000..bbd31a046 --- /dev/null +++ b/src/js/actions/PositionActions.js @@ -0,0 +1,17 @@ +'use strict'; +var AppDispatcher = require('../dispatcher/AppDispatcher'); +var PositionConstants = require('../constants/BallotConstants'); + +// In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ VOTER_SUPPORTING_SAVE) +// When action calls one of these functions, we are telling the code in the AppDispatcher block to run +module.exports = { + + positionRetrieved: function( we_vote_id, payload) { + AppDispatcher.dispatch({ + actionType: PositionConstants.POSITION_RETRIEVED, + payload: payload, + we_vote_id: we_vote_id + }); + }, + +}; diff --git a/src/js/components/Ballot/OrganizationItem.jsx b/src/js/components/Ballot/PositionItem.jsx similarity index 55% rename from src/js/components/Ballot/OrganizationItem.jsx rename to src/js/components/Ballot/PositionItem.jsx index 4ae3d20e6..abb06641b 100644 --- a/src/js/components/Ballot/OrganizationItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -1,33 +1,39 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; -import BallotActions from '../../actions/BallotActions'; -import BallotStore from '../../stores/BallotStore'; +import PositionActions from '../../actions/PositionActions'; +import PositionStore from '../../stores/PositionStore'; -export default class OrganizationItem extends Component { +export default class PositionItem extends Component { static propTypes = { position_we_vote_id: PropTypes.string.isRequired, }; constructor (props) { super(props); - this.state = { organization: this.props}; + this.state = { position: {} }; } componentDidMount () { - BallotStore.addChangeListener(this._onChange.bind(this)); + console.log("Position Item Component Mounted with wevoteid:") + console.log(this.props.position_we_vote_id); + PositionStore.retrievePositionByWeVoteId(this.props.position_we_vote_id); + PositionStore.addChangeListener(this._onChange.bind(this)); } componentWillUnmount () { - BallotStore.removeChangeListener(this._onChange.bind(this)); + PositionStore.removeChangeListener(this._onChange.bind(this)); } _onChange () { - // this.setState({ organization: BallotStore.getOrganization(this.props.candidate_we_vote_id, this.props.item.position_we_vote_id) }); + this.setState({ position: PositionStore.getLocalPositionByWeVoteId(this.props.position_we_vote_id) }); + console.log("This Position Item:") + console.log(this.state.position); } render() { - var organization = this.state.organization; - var supportText = organization.is_oppose ? "Opposes" : "Supports"; + // console.log(this.state.position); + var position = this.state.position; + var supportText = position.is_oppose ? "Opposes" : "Supports"; return (
  • @@ -40,16 +46,15 @@ export default class OrganizationItem extends Component {

    - { organization.speaker_label } + params={{id: position.speaker_id, org_id: position.speaker_we_vote_id}}> + { position.speaker_label }

    {supportText} Yesterday at 7:18 PM

  • - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) + {position.statement_text}

    23 Likes
    diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx new file mode 100644 index 000000000..f9fc1de27 --- /dev/null +++ b/src/js/components/Ballot/PositionList.jsx @@ -0,0 +1,37 @@ +import React, { Component, PropTypes } from 'react'; +import BallotStore from '../../stores/BallotStore'; +import PositionItem from './PositionItem'; + +export default class PositionList extends Component { + static propTypes = { + we_vote_id: PropTypes.string.isRequired + }; + + constructor(props) { + super(props); + this.state = { position_list: [] }; + } + // no candidate exists... go to ballot + componentDidMount(){ + BallotStore.fetchCandidatePositionsByWeVoteId(this.props.we_vote_id) + BallotStore.addChangeListener(this._onChange.bind(this)); + } + + componentWillUnmount(){ + BallotStore.removeChangeListener(this._onChange.bind(this)); + } + + _onChange(){ + this.setState({ position_list: BallotStore.getCandidateByWeVoteId(this.props.we_vote_id).position_list }); + } + + render() { + return ( +
      + { this.state.position_list.map( item => + ) + } +
    + ); + } +} diff --git a/src/js/constants/BallotConstants.js b/src/js/constants/BallotConstants.js index ba7f6c997..52c3bf062 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -6,6 +6,5 @@ module.exports = require('keymirror')({ VOTER_STOP_OPPOSING_SAVE: null, VOTER_STAR_ON_SAVE: null, VOTER_STAR_OFF_SAVE: null, - VOTER_STAR_OFF_SAVE: null, - DOWNLOAD_ORGANIZATIONS: null + POSITIONS_RETRIEVED: null }); diff --git a/src/js/constants/PositionConstants.js b/src/js/constants/PositionConstants.js new file mode 100644 index 000000000..16ea7fb3f --- /dev/null +++ b/src/js/constants/PositionConstants.js @@ -0,0 +1,3 @@ +module.exports = require('keymirror')({ + POSITION_RETRIEVED: null +}); diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index b9402ca23..95fec06e0 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -2,10 +2,9 @@ import React, { Component, PropTypes } from 'react'; import { Button, ButtonToolbar, DropdownButton, Input, MenuItem, Navbar } from "react-bootstrap"; import { Link } from 'react-router'; -import BallotActions from '../../actions/BallotActions'; import BallotStore from '../../stores/BallotStore'; import CandidateDetail from '../../components/Ballot/CandidateDetail'; -import OrganizationItem from '../../components/Ballot/OrganizationItem'; +import PositionList from '../../components/Ballot/PositionList'; import ItemActionbar from '../../components/ItemActionbar'; import ItemActionBar2 from '../../components/ItemActionBar2'; import StarAction from '../../components/StarAction'; @@ -17,38 +16,25 @@ export default class Candidate extends Component { constructor(props) { super(props); - this.state = { candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }; - } - // no candidate exists... go to ballot - componentDidMount(){ - if (Object.keys(this.state.candidate).length === 0){ - this.props.history.replace('/ballot'); - } - console.log('Candidate Component Mounted!') - BallotActions.downloadOrganizations(this.props.params.we_vote_id); - BallotStore.addChangeListener(this._onChange.bind(this)); + this.state = { candidate: {} }; } - componentWillUnmount(){ - BallotStore.removeChangeListener(this._onChange.bind(this)); - } - - _onChange(){ - console.log('onChange!'); - var we_vote_id = this.props.params.we_vote_id; - this.setState({ candidate: BallotStore.getCandidateByWeVoteId(we_vote_id) }); + componentDidMount(){ + this.setState({ candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }); } render() { - var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); - if (!candidate){return (
    );} - // var candidate = this.state.candidate; - console.log("Candidate Object:"); - console.log(candidate); - var position_list = candidate ? candidate.position_list : undefined; + // if (Object.keys(this.state.candidate).length === 0){ + // this.props.history.replace('/ballot'); + // }; + var candidate = this.state.candidate; + var we_vote_id = this.props.params.we_vote_id; + if (!candidate.we_vote_id){ + return (
    ); + }; - if (Object.keys(candidate).length === 0){ - this.props.history.replace('/ballot');} + // if (Object.keys(candidate).length === 0){ + // this.props.history.replace('/ballot');} var support_item; if (this.props.support_on) { @@ -134,14 +120,7 @@ export default class Candidate extends Component { -
      - { (position_list) ? position_list.map( item => - - ) : (
      ) - } -
    +
    diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 50f31b26c..69eec895f 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -4,6 +4,7 @@ import { shallowClone } from '../utils/object-utils'; const AppDispatcher = require('../dispatcher/AppDispatcher'); const BallotConstants = require('../constants/BallotConstants'); +const BallotActions = require('../actions/BallotActions'); let _ballot_store = {}; let _ballot_order_ids = []; @@ -48,16 +49,14 @@ const BallotAPIWorker = { }); }, - positionListForBallotItem : function( we_vote_id, success_func) { + positionListForBallotItem : function( we_vote_id, success) { return service.get({ endpoint: 'positionListForBallotItem', query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item }, - success: function(res){ - success_func(res); - } + success }); }, @@ -359,30 +358,25 @@ const BallotStore = createStore({ return temp; }, - // emitChange: function () { - // this.emit(BALLOT_CHANGE_EVENT); - // }, - // - // addChangeListener: function(callback) { - // console.log("Change listener added"); - // this.on(BALLOT_CHANGE_EVENT, callback); - // }, - // - // removeChangeListener: function(callback) { - // console.log("Change listener removed!"); - // this.removeListener(BALLOT_CHANGE_EVENT, callback); - // }, - - getCandidatePositionsByWeVoteId: function(candidate_we_vote_id){ - BallotAPIWorker.positionListForBallotItem(candidate_we_vote_id, function(res){ - var we_vote_id = candidate_we_vote_id; - _ballot_store[we_vote_id].position_list = res.position_list; - BallotStore.emitChange(); - }.bind(this)) + emitChange: function () { + this.emit(BALLOT_CHANGE_EVENT); + }, + + addChangeListener: function(callback) { + // console.log("Change listener added"); + this.on(BALLOT_CHANGE_EVENT, callback); }, - getOrganization: function(candidate_we_vote_id, organization_we_vote_id){ - return _ballot_store[candidate_we_vote_id].position_list.organization_we_vote_id; + removeChangeListener: function(callback) { + // console.log("Change listener removed!"); + this.removeListener(BALLOT_CHANGE_EVENT, callback); + }, + + fetchCandidatePositionsByWeVoteId: function(candidate_we_vote_id){ + BallotAPIWorker.positionListForBallotItem(candidate_we_vote_id, + function(res){ + BallotActions.positionsRetrieved(candidate_we_vote_id, res); + }); }, /** @@ -470,6 +464,11 @@ const BallotStore = createStore({ } }); +function setLocalPositionsList(we_vote_id, position_list) { + _ballot_store[we_vote_id].position_list = position_list; + return true; +} + /** * toggle the star state of a ballot item by its we_vote_id * @param {string} we_vote_id identifier for lookup in stored @@ -546,8 +545,9 @@ AppDispatcher.register( action => { var { we_vote_id } = action; switch (action.actionType) { - case BallotConstants.DOWNLOAD_ORGANIZATIONS: - BallotStore.getCandidatePositionsByWeVoteId(we_vote_id); + case BallotConstants.POSITIONS_RETRIEVED: + setLocalPositionsList(action.we_vote_id, action.payload.position_list) + && BallotStore.emitChange(); break; case BallotConstants.VOTER_SUPPORTING_SAVE: diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js new file mode 100644 index 000000000..cc844492c --- /dev/null +++ b/src/js/stores/PositionStore.js @@ -0,0 +1,81 @@ +import service from '../utils/service'; +import { createStore } from '../utils/createStore'; +import { shallowClone } from '../utils/object-utils'; + +const AppDispatcher = require('../dispatcher/AppDispatcher'); +const PositionConstants = require('../constants/PositionConstants'); +const PositionActions = require('../actions/PositionActions'); + +var _position_store = {}; // All positions that have been fetched (by we_vote_ids) + +function printErr (err) { + console.error(err); +} + +const POSITION_CHANGE_EVENT = 'POSITION_CHANGE_EVENT'; + +const PositionAPIWorker = { + + positionRetrieve: function (we_vote_id, success) { + return service.get({ + endpoint: 'positionRetrieve', + query: { + position_we_vote_id: we_vote_id, + }, + success + }); + } + +}; + +const PositionStore = createStore({ + + emitChange: function () { + this.emit(POSITION_CHANGE_EVENT); + }, + + addChangeListener: function(callback) { + // console.log("Change listener added"); + this.on(POSITION_CHANGE_EVENT, callback); + }, + + removeChangeListener: function(callback) { + // console.log("Change listener removed!"); + this.removeListener(POSITION_CHANGE_EVENT, callback); + }, + +retrievePositionByWeVoteId: function(we_vote_id){ + PositionAPIWorker.positionRetrieve(we_vote_id, + function(res){ + PositionActions.positionRetrieved(we_vote_id, res); + console.log("Response"); + console.log(res); + }); +}, + +getLocalPositionByWeVoteId: function(we_vote_id){ + return shallowClone(_position_store[we_vote_id]); +} + +}); + +function setLocalPosition(we_vote_id, position) { + _ballot_store[we_vote_id] = position; + return true; +} + +AppDispatcher.register( action => { + var { we_vote_id } = action; + switch (action.actionType) { + + case PositionConstants.POSITION_RETRIEVED: + console.log("Action Payload:"); + console.log(action.payload); + setLocalPosition(action.we_vote_id, action.payload ) + && PositionStore.emitChange(); + break; + + } +}); + +export default PositionStore; From fbed1c56818d8a76caf108a0e87064c538aca741 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 15 Feb 2016 19:13:53 -0800 Subject: [PATCH 26/92] Add speaker label --- src/js/actions/PositionActions.js | 2 +- src/js/components/Ballot/PositionItem.jsx | 9 +++------ src/js/components/Ballot/PositionList.jsx | 3 ++- src/js/routes/Ballot/Candidate.jsx | 9 +++++++++ src/js/stores/PositionStore.js | 5 ++--- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/js/actions/PositionActions.js b/src/js/actions/PositionActions.js index bbd31a046..67a33be9c 100644 --- a/src/js/actions/PositionActions.js +++ b/src/js/actions/PositionActions.js @@ -1,6 +1,6 @@ 'use strict'; var AppDispatcher = require('../dispatcher/AppDispatcher'); -var PositionConstants = require('../constants/BallotConstants'); +var PositionConstants = require('../constants/PositionConstants'); // In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ VOTER_SUPPORTING_SAVE) // When action calls one of these functions, we are telling the code in the AppDispatcher block to run diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index abb06641b..95b6440b3 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -10,12 +10,10 @@ export default class PositionItem extends Component { constructor (props) { super(props); - this.state = { position: {} }; + this.state = { position: {}, organization: {} }; } componentDidMount () { - console.log("Position Item Component Mounted with wevoteid:") - console.log(this.props.position_we_vote_id); PositionStore.retrievePositionByWeVoteId(this.props.position_we_vote_id); PositionStore.addChangeListener(this._onChange.bind(this)); } @@ -26,12 +24,11 @@ export default class PositionItem extends Component { _onChange () { this.setState({ position: PositionStore.getLocalPositionByWeVoteId(this.props.position_we_vote_id) }); - console.log("This Position Item:") + console.log("Position:") console.log(this.state.position); } render() { - // console.log(this.state.position); var position = this.state.position; var supportText = position.is_oppose ? "Opposes" : "Supports"; return ( @@ -47,7 +44,7 @@ export default class PositionItem extends Component {

    - { position.speaker_label } + { this.props.speaker_label }

    {supportText} Yesterday at 7:18 PM

    diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index f9fc1de27..6089eb54e 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -23,13 +23,14 @@ export default class PositionList extends Component { _onChange(){ this.setState({ position_list: BallotStore.getCandidateByWeVoteId(this.props.we_vote_id).position_list }); + console.log(this.state.position_list); } render() { return (
      { this.state.position_list.map( item => - ) + ) }
    ); diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 95fec06e0..f541c992f 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -19,6 +19,15 @@ export default class Candidate extends Component { this.state = { candidate: {} }; } + componentWillMount(){ + // Redirects to root if candidate isn't fetched yet; TODO: just fetch params to enable sending links to candidate page. + var candidate = BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id); + if (Object.keys(candidate).length === 0) + { + this.props.history.replace('/ballot'); + } + } + componentDidMount(){ this.setState({ candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }); } diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js index cc844492c..b411e3233 100644 --- a/src/js/stores/PositionStore.js +++ b/src/js/stores/PositionStore.js @@ -60,7 +60,7 @@ getLocalPositionByWeVoteId: function(we_vote_id){ }); function setLocalPosition(we_vote_id, position) { - _ballot_store[we_vote_id] = position; + _position_store[we_vote_id] = position; return true; } @@ -69,13 +69,12 @@ AppDispatcher.register( action => { switch (action.actionType) { case PositionConstants.POSITION_RETRIEVED: - console.log("Action Payload:"); - console.log(action.payload); setLocalPosition(action.we_vote_id, action.payload ) && PositionStore.emitChange(); break; } + }); export default PositionStore; From a188a4eace2c06d70456a5e180bf5267f52b774c Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 16 Feb 2016 00:11:54 -0500 Subject: [PATCH 27/92] Overhaul README adding logo The README for this repo should only need to have docs for setting up the web application with a link to the WeVoteServer at bottom --- README.md | 68 +++++++++++++------------------------------------ wevotelogo.png | Bin 0 -> 82552 bytes 2 files changed, 18 insertions(+), 50 deletions(-) create mode 100644 wevotelogo.png diff --git a/README.md b/README.md index 5f58d0d8a..fed94c8e0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# README for We Vote WebApp +#We Vote USA Web/Mobile Web Application + +![WeVoteUS](wevotelogo.png) [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) | -[![Coverage Status](https://coveralls.io/repos/github/wevote/WebApp/badge.svg?branch=master)](https://coveralls.io/github/wevote/WebApp?branch=develop) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a @@ -10,68 +11,35 @@ social way to interact with ballot data. You can see our current wireframe mockup for a San Francisco ballot here: http://start.wevoteusa.org/ -## Install WeVoteServer First -In order to get the data the WebApp needs, please -[install WeVoteServer](https://github.com/wevote/WeVoteServer/blob/master/README_API_INSTALL.md) - - -## Install nodeenv ("Node Env") - -Install nodeenv globally. For instructions installing it locally, see: https://github.com/ekalinin/nodeenv - - $ cd ~ - $ sudo pip install nodeenv - -Create a place for your WebApp virtual environment to live on your hard drive. We recommend installing it -away from the WebApp source code: - - $ mkdir /Users//NodeEnvironments/ - $ cd /Users//NodeEnvironments/ - -Now create a new virtual environment in that 'NodeEnvironments' folder. This can take many minutes. - - $ nodeenv WebAppEnv - -Now activate this new virtual environment: +## Getting started - $ cd /Users//NodeEnvironments/WebAppEnv/ - $ . bin/activate +1. Fork the repository to your GitHub repo. +2. Clone your repository to your local machine +3. **Always** start from the develop branch +4. When working on features or hotfixes, created a new branch and push those for pull requests - [See contributing for more](CONTRIBUTING.md) +5. Request a pull request. If there is an issue that the pull request is tried to, include the number of the issue in your description. -Confirm the versions of your main packages are >= to these versions: +Before starting local development, confirm the versions of your main packages are >= to these versions: - (WebAppEnv) $ node -v + $ node -v v5.3.0 - (WebAppEnv) $ npm -v + $ npm -v 3.3.12 - -## Clone https://github.com/wevote/WebApp - -Create a place to put all of the code from Github: - - $ mkdir /Users//MyProjects/ - -Retrieve “WebApp” into that folder, so your code ends up here: - - /Users//MyProjects/WebApp - - ## Install and start web application - (WebAppEnv) $ cd /Users//MyProjects/WebApp - (WebAppEnv) $ npm -g install gulp-cli // try sudo if it does not work - (WebAppEnv) $ npm install - (WebAppEnv) $ gulp + $ cd /Users//MyProjects/WebApp + $ npm -g install gulp-cli // try sudo if it does not work + $ npm install + $ gulp You should be able to visit WebApp here: http://localhost:3000 - -## After Installation: Working with WebApp Day-to-Day - -[Read about working with WebApp on a daily basis](README_WORKING_WITH_WEB_APP.md) +## Using WeVote Backend Locally +First, start by reading the instructions[install WeVoteServer](https://github.com/wevote/WeVoteServer/blob/master/README_API_INSTALL.md) ## SemVer diff --git a/wevotelogo.png b/wevotelogo.png new file mode 100644 index 0000000000000000000000000000000000000000..2971796a628292738a819da0b89486b7330e6700 GIT binary patch literal 82552 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�TPe72WAk`M52 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 Date: Tue, 16 Feb 2016 07:31:33 -0800 Subject: [PATCH 28/92] Code Review cleanup changes --- src/js/actions/BallotActions.js | 2 +- src/js/actions/PositionActions.js | 2 +- src/js/components/Ballot/PositionItem.jsx | 2 +- src/js/components/Ballot/PositionList.jsx | 10 +++++----- src/js/stores/BallotStore.js | 15 --------------- src/js/stores/PositionStore.js | 22 +++------------------- 6 files changed, 11 insertions(+), 42 deletions(-) diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index 548293d74..536bd14c4 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -6,7 +6,7 @@ var BallotConstants = require('../constants/BallotConstants'); // When action calls one of these functions, we are telling the code in the AppDispatcher block to run module.exports = { - positionsRetrieved: function( we_vote_id, payload) { + positionsRetrieved: function (we_vote_id, payload) { AppDispatcher.dispatch({ actionType: BallotConstants.POSITIONS_RETRIEVED, payload: payload, diff --git a/src/js/actions/PositionActions.js b/src/js/actions/PositionActions.js index 67a33be9c..beee9c2aa 100644 --- a/src/js/actions/PositionActions.js +++ b/src/js/actions/PositionActions.js @@ -6,7 +6,7 @@ var PositionConstants = require('../constants/PositionConstants'); // When action calls one of these functions, we are telling the code in the AppDispatcher block to run module.exports = { - positionRetrieved: function( we_vote_id, payload) { + positionRetrieved: function (we_vote_id, payload) { AppDispatcher.dispatch({ actionType: PositionConstants.POSITION_RETRIEVED, payload: payload, diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 95b6440b3..fee773869 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -28,7 +28,7 @@ export default class PositionItem extends Component { console.log(this.state.position); } - render() { + render () { var position = this.state.position; var supportText = position.is_oppose ? "Opposes" : "Supports"; return ( diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index 6089eb54e..3aea58c65 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -7,26 +7,26 @@ export default class PositionList extends Component { we_vote_id: PropTypes.string.isRequired }; - constructor(props) { + constructor (props) { super(props); this.state = { position_list: [] }; } // no candidate exists... go to ballot - componentDidMount(){ + componentDidMount (){ BallotStore.fetchCandidatePositionsByWeVoteId(this.props.we_vote_id) BallotStore.addChangeListener(this._onChange.bind(this)); } - componentWillUnmount(){ + componentWillUnmount (){ BallotStore.removeChangeListener(this._onChange.bind(this)); } - _onChange(){ + _onChange (){ this.setState({ position_list: BallotStore.getCandidateByWeVoteId(this.props.we_vote_id).position_list }); console.log(this.state.position_list); } - render() { + render () { return (
      { this.state.position_list.map( item => diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 69eec895f..ba5cf0b5e 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -11,7 +11,6 @@ let _ballot_order_ids = []; let _google_civic_election_id = null; const MEASURE = 'MEASURE'; -var BALLOT_CHANGE_EVENT = 'BALLOT_CHANGE_EVENT'; function addItemsToBallotStore (ballot_item_list) { ballot_item_list.forEach( ballot_item => { @@ -358,20 +357,6 @@ const BallotStore = createStore({ return temp; }, - emitChange: function () { - this.emit(BALLOT_CHANGE_EVENT); - }, - - addChangeListener: function(callback) { - // console.log("Change listener added"); - this.on(BALLOT_CHANGE_EVENT, callback); - }, - - removeChangeListener: function(callback) { - // console.log("Change listener removed!"); - this.removeListener(BALLOT_CHANGE_EVENT, callback); - }, - fetchCandidatePositionsByWeVoteId: function(candidate_we_vote_id){ BallotAPIWorker.positionListForBallotItem(candidate_we_vote_id, function(res){ diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js index b411e3233..4057f81af 100644 --- a/src/js/stores/PositionStore.js +++ b/src/js/stores/PositionStore.js @@ -12,8 +12,6 @@ function printErr (err) { console.error(err); } -const POSITION_CHANGE_EVENT = 'POSITION_CHANGE_EVENT'; - const PositionAPIWorker = { positionRetrieve: function (we_vote_id, success) { @@ -30,21 +28,7 @@ const PositionAPIWorker = { const PositionStore = createStore({ - emitChange: function () { - this.emit(POSITION_CHANGE_EVENT); - }, - - addChangeListener: function(callback) { - // console.log("Change listener added"); - this.on(POSITION_CHANGE_EVENT, callback); - }, - - removeChangeListener: function(callback) { - // console.log("Change listener removed!"); - this.removeListener(POSITION_CHANGE_EVENT, callback); - }, - -retrievePositionByWeVoteId: function(we_vote_id){ +retrievePositionByWeVoteId: function (we_vote_id){ PositionAPIWorker.positionRetrieve(we_vote_id, function(res){ PositionActions.positionRetrieved(we_vote_id, res); @@ -53,13 +37,13 @@ retrievePositionByWeVoteId: function(we_vote_id){ }); }, -getLocalPositionByWeVoteId: function(we_vote_id){ +getLocalPositionByWeVoteId: function (we_vote_id){ return shallowClone(_position_store[we_vote_id]); } }); -function setLocalPosition(we_vote_id, position) { +function setLocalPosition (we_vote_id, position) { _position_store[we_vote_id] = position; return true; } From 8ea8085f378ddcbc26104b30f8f2a1b218358438 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 16 Feb 2016 12:11:08 -0500 Subject: [PATCH 29/92] committing jquery vendor scripts --- src/js/vendor/jquery.js | 4434 +++++++++++++++++++++++++++++++++++ src/js/vendor/jquery.min.js | 3 + 2 files changed, 4437 insertions(+) create mode 100644 src/js/vendor/jquery.js create mode 100644 src/js/vendor/jquery.min.js diff --git a/src/js/vendor/jquery.js b/src/js/vendor/jquery.js new file mode 100644 index 000000000..ca27e30e1 --- /dev/null +++ b/src/js/vendor/jquery.js @@ -0,0 +1,4434 @@ +/*! + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 23 12:48:35 2012 -0800 + */ +var j= (function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + marginDiv, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
      a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "
      " + + "" + + "
      "; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
      t
      "; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
      "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, attr, name, + data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + jQuery._data( this[0], "parsedAttrs", true ); + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var self = jQuery( this ), + args = [ parts[0], value ]; + + self.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + + + +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rhash = /#.*$/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL + rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rquery = /\?/, + rscript = /)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rspacesAjax = /\s+/, + rts = /([?&])_=[^&]*/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Document location + ajaxLocation, + + // Document location segments + ajaxLocParts, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = ["*/"] + ["*"]; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + if ( jQuery.isFunction( func ) ) { + var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), + i = 0, + length = dataTypes.length, + dataType, + list, + placeBefore; + + // For each dataType in the dataTypeExpression + for ( ; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ), + selection; + + for ( ; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } +} + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf( " " ); + if ( off >= 0 ) { + var selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + // Complete callback (responseText is used internally) + complete: function( jqXHR, status, responseText ) { + // Store the response as specified by the jqXHR object + responseText = jqXHR.responseText; + // If successful, inject the HTML into all the matched elements + if ( jqXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jqXHR.done(function( r ) { + responseText = r; + }); + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("
      ") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + responseText ); + } + + if ( callback ) { + self.each( callback, [ responseText, status, jqXHR ] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.on( o, f ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +}); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + if ( settings ) { + // Building a settings object + ajaxExtend( target, jQuery.ajaxSettings ); + } else { + // Extending ajaxSettings + settings = target; + target = jQuery.ajaxSettings; + } + ajaxExtend( target, settings ); + return target; + }, + + ajaxSettings: { + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + traditional: false, + headers: {}, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": allTypes + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + context: true, + url: true + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // ifModified key + ifModifiedKey, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // The jqXHR state + state = 0, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || "abort"; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, nativeStatusText, responses, headers ) { + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + var isSuccess, + success, + error, + statusText = nativeStatusText, + response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, + lastModified, + etag; + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { + jQuery.lastModified[ ifModifiedKey ] = lastModified; + } + if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { + jQuery.etag[ ifModifiedKey ] = etag; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + try { + success = ajaxConvert( s, response ); + statusText = "success"; + isSuccess = true; + } catch(e) { + // We have a parsererror + statusText = "parsererror"; + error = e; + } + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = "" + ( nativeStatusText || statusText ); + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.add; + + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for ( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.then( tmp, tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); + + // Determine if a cross-domain request is in order + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefiler, stop there + if ( state === 2 ) { + return false; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); + } + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already + jqXHR.abort(); + return false; + + } + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + return jqXHR; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : value; + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + // Serialize object item. + for ( var name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields, + ct, + type, + finalDataType, + firstDataType; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + var dataTypes = s.dataTypes, + converters = {}, + i, + key, + length = dataTypes.length, + tmp, + // Current and previous dataTypes + current = dataTypes[ 0 ], + prev, + // Conversion expression + conversion, + // Conversion function + conv, + // Conversion functions (transitive conversion) + conv1, + conv2; + + // For each dataType in the chain + for ( i = 1; i < length; i++ ) { + + // Create converters map + // with lowercased keys + if ( i === 1 ) { + for ( key in s.converters ) { + if ( typeof key === "string" ) { + converters[ key.toLowerCase() ] = s.converters[ key ]; + } + } + } + + // Get the dataTypes + prev = current; + current = dataTypes[ i ]; + + // If current is auto dataType, update it to prev + if ( current === "*" ) { + current = prev; + // If no auto and dataTypes are actually different + } else if ( prev !== "*" && prev !== current ) { + + // Get the converter + conversion = prev + " " + current; + conv = converters[ conversion ] || converters[ "* " + current ]; + + // If there is no direct converter, search transitively + if ( !conv ) { + conv2 = undefined; + for ( conv1 in converters ) { + tmp = conv1.split( " " ); + if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { + conv2 = converters[ tmp[1] + " " + current ]; + if ( conv2 ) { + conv1 = converters[ conv1 ]; + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + break; + } + } + } + } + // If we found no converter, dispatch an error + if ( !( conv || conv2 ) ) { + jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); + } + // If found converter is not an equivalence + if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly + response = conv ? conv( response ) : conv2( conv1(response) ); + } + } + } + return response; +} + + + + +var jsc = jQuery.now(), + jsre = /(\=)\?(&|$)|\?\?/i; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + return jQuery.expando + "_" + ( jsc++ ); + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var inspectData = s.contentType === "application/x-www-form-urlencoded" && + ( typeof s.data === "string" ); + + if ( s.dataTypes[ 0 ] === "jsonp" || + s.jsonp !== false && ( jsre.test( s.url ) || + inspectData && jsre.test( s.data ) ) ) { + + var responseContainer, + jsonpCallback = s.jsonpCallback = + jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( inspectData ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } + } + + s.url = url; + s.data = data; + + // Install callback + window[ jsonpCallback ] = function( response ) { + responseContainer = [ response ]; + }; + + // Clean-up function + jqXHR.always(function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( previous ) ) { + window[ jsonpCallback ]( responseContainer[ 0 ] ); + } + }); + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Delegate to script + return "script"; + } +}); + + + + +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +}); + + + + +var // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); + } + } : false, + xhrId = 0, + xhrCallbacks; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var xhr = s.xhr(), + handle, + i; + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occured + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + responses.text = xhr.responseText; + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + // if we're in sync mode or it's in cache + // and has been retrieved directly (IE6 & IE7) + // we need to manually fire the callback + if ( !s.async || xhr.readyState === 4 ) { + callback(); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} + + + + +// DO NOT Expose jQuery to the global object +// window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + +module.exports = jQuery + +})( window ); diff --git a/src/js/vendor/jquery.min.js b/src/js/vendor/jquery.min.js new file mode 100644 index 000000000..f41bd6dbd --- /dev/null +++ b/src/js/vendor/jquery.min.js @@ -0,0 +1,3 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function bf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function be(){try{return new a.XMLHttpRequest}catch(b){}}function $(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
      "+""+"
      ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
      t
      ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
      ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){t=p.delegateType||h,m=s.test(t+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,t]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,t])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),q.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),r.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)});var z=/%20/g,A=/\[\]$/,B=/\r?\n/g,C=/#.*$/,D=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,E=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,F=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,G=/^(?:GET|HEAD)$/,H=/^\/\//,I=/\?/,J=/)<[^<]*)*<\/script>/gi,K=/^(?:select|textarea)/i,L=/\s+/,M=/([?&])_=[^&]*/,N=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,O=f.fn.load,P={},Q={},R,S,T=["*/"]+["*"];try{R=e.href}catch(U){R=c.createElement("a"),R.href="",R=R.href}S=N.exec(R.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&O)return O.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length +);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(J,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||K.test(this.nodeName)||E.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(B,"\r\n")}}):{name:b.name,value:c.replace(B,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?X(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),X(a,b);return a},ajaxSettings:{url:R,isLocal:F.test(S[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":T},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:V(P),ajaxTransport:V(Q),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?Z(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=D.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(C,"").replace(H,S[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(L),d.crossDomain==null&&(r=N.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==S[1]&&r[2]==S[2]&&(r[3]||(r[1]==="http:"?80:443))==(S[3]||(S[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),W(P,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!G.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(I.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(M,"$1_="+x);d.url=y+(y===d.url?(I.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+T+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=W(Q,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)Y(g,a[g],c,e);return d.join("&").replace(z,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var _=f.now(),ba=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ba.test(b.url)||e&&ba.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ba,l),b.url===j&&(e&&(k=k.replace(ba,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bb=a.ActiveXObject?function(){for(var a in bd)bd[a](0,1)}:!1,bc=0,bd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&be()||bf()}:be,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,bb&&delete bd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++bc,bb&&(bd||(bd={},f(a).unload(bb)),bd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file From 20a3844480b562a6aa7cfc3bc6d63f00fc54cc7a Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 16 Feb 2016 11:41:00 -0500 Subject: [PATCH 30/92] app workflow and structure --- .eslintrc | 253 +++++++++++++++++++++++----- CHANGELOG.md | 8 + CONTRIBUTING.md | 15 ++ README.md | 23 ++- README_WORKING_WITH_WEB_APP.md | 10 ++ __tests__/Application-test.js | 18 -- docs/contributing/index.md | 108 ++++++++++++ docs/contributing/versions/index.md | 84 +++++++++ package.json | 29 ++-- 9 files changed, 464 insertions(+), 84 deletions(-) delete mode 100644 __tests__/Application-test.js create mode 100644 docs/contributing/index.md create mode 100644 docs/contributing/versions/index.md diff --git a/.eslintrc b/.eslintrc index e470699e7..2f9674cb8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,49 +1,212 @@ { - "parser": "babel-eslint", - "env": { - "node": true, - "browser": true - }, - "ecmaFeatures": { - "arrowFunctions": true, - "blockBindings": true, - "classes": true, - "defaultParams": true, - "destructuring": true, - "forOf": true, - "modules": true, - "objectLiteralComputedProperties": true, - "objectLiteralShorthandMethods": true, - "objectLiteralShorthandProperties": true, - "spread": true, - "superInFunctions": true, - "templateStrings": true, - "unicodeCodePointEscapes": true, - "jsx": true - }, - "rules": { - "react/jsx-boolean-value": 2, - "react/jsx-quotes": 2, - "react/jsx-no-undef": 2, - "react/jsx-sort-props": 0, - "react/jsx-sort-prop-types": 0, - "react/jsx-uses-react": 2, - "react/jsx-uses-vars": 2, - "react/no-did-mount-set-state": 0, - "react/no-did-update-set-state": 2, - "react/no-multi-comp": 2, - "react/no-unknown-property": 1, - "react/prop-types": 1, - "react/react-in-jsx-scope": 2, - "react/self-closing-comp": 2, - "react/wrap-multilines": 0, - "jsx-quotes": 1, - "quotes": 0 - }, - "plugins": [ - "react" - ], + "parser": "babel-eslint", + + "env": { + "browser": true, + "node": true, + "es6": true + }, + + "ecmaFeatures": { + "arrowFunctions": true, + "binaryLiterals": true, + "blockBindings": true, + "classes": false, + "defaultParams": true, + "destructuring": true, + "forOf": true, + "generators": true, + "modules": true, + "objectLiteralComputedProperties": true, + "objectLiteralDuplicateProperties": true, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "octalLiterals": true, + "regexUFlag": true, + "regexYFlag": true, + "spread": true, + "superInFunctions": false, + "templateStrings": true, + "unicodeCodePointEscapes": true, + "globalReturn": true, + "jsx": true + }, + + "rules": { + "block-scoped-var": [0], + "brace-style": [2, "1tbs", {"allowSingleLine": true}], + "camelcase": [0], + "comma-dangle": [0], + "comma-spacing": [2], + "comma-style": [2, "last"], + "complexity": [0, 11], + "consistent-return": [2], + "consistent-this": [0, "that"], + "curly": [2, "multi-line"], + "default-case": [2], + "dot-notation": [2, {"allowKeywords": true}], + "eol-last": [2], + "eqeqeq": [2], + "func-names": [0], + "func-style": [0, "declaration"], + "generator-star-spacing": [2, "after"], + "guard-for-in": [0], + "handle-callback-err": [0], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "quotes": [2, "single", "avoid-escape"], + "max-depth": [0, 4], + "max-len": [0, 80, 4], + "max-nested-callbacks": [0, 2], + "max-params": [0, 3], + "max-statements": [0, 10], + "new-parens": [2], + "new-cap": [0], + "newline-after-var": [0], + "no-alert": [2], + "no-array-constructor": [2], + "no-bitwise": [0], + "no-caller": [2], + "no-catch-shadow": [2], + "no-cond-assign": [2], + "no-console": [0], + "no-constant-condition": [1], + "no-continue": [2], + "no-control-regex": [2], + "no-debugger": [2], + "no-delete-var": [2], + "no-div-regex": [0], + "no-dupe-args": [2], + "no-dupe-keys": [2], + "no-duplicate-case": [2], + "no-else-return": [0], + "no-empty": [2], + "no-empty-character-class": [2], + "no-empty-label": [2], + "no-eq-null": [0], + "no-eval": [2], + "no-ex-assign": [2], + "no-extend-native": [1], + "no-extra-bind": [2], + "no-extra-boolean-cast": [2], + "no-extra-semi": [1], + "no-fallthrough": [2], + "no-floating-decimal": [2], + "no-func-assign": [2], + "no-implied-eval": [2], + "no-inline-comments": [0], + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": [2], + "no-irregular-whitespace": [2], + "no-iterator": [2], + "no-label-var": [2], + "no-labels": [2], + "no-lone-blocks": [2], + "no-lonely-if": [2], + "no-loop-func": [2], + "no-mixed-requires": [0, false], + "no-mixed-spaces-and-tabs": [2, false], + "no-multi-spaces": [2], + "no-multi-str": [2], + "no-multiple-empty-lines": [2, {"max": 2}], + "no-native-reassign": [1], + "no-negated-in-lhs": [2], + "no-nested-ternary": [0], + "no-new": [2], + "no-new-func": [2], + "no-new-object": [2], + "no-new-require": [0], + "no-new-wrappers": [2], + "no-obj-calls": [2], + "no-octal": [2], + "no-octal-escape": [2], + "no-param-reassign": [2], + "no-path-concat": [0], + "no-plusplus": [0], + "no-process-env": [0], + "no-process-exit": [2], + "no-proto": [2], + "no-redeclare": [2], + "no-regex-spaces": [2], + "no-reserved-keys": [0], + "no-restricted-modules": [0], + "no-return-assign": [2], + "no-script-url": [2], + "no-self-compare": [0], + "no-sequences": [2], + "no-shadow": [2], + "no-shadow-restricted-names": [2], + "no-spaced-func": [2], + "no-sparse-arrays": [2], + "no-sync": [0], + "no-ternary": [0], + "no-throw-literal": [2], + "no-trailing-spaces": [2], + "no-undef": [2], + "no-undef-init": [2], + "no-undefined": [0], + "no-underscore-dangle": [2], + "no-unreachable": [2], + "no-unused-expressions": [2], + "no-unused-vars": [1, {"vars": "all", "args": "after-used"}], + "no-use-before-define": [2], + "no-void": [0], + "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}], + "no-with": [2], + "no-extra-parens": [2], + "one-var": [0], + "operator-assignment": [0, "always"], + "operator-linebreak": [2, "after"], + "padded-blocks": [0], + "quote-props": [0], + "radix": [0], + "semi": [2], + "semi-spacing": [2, {"before": false, "after": true}], + "sort-vars": [0], + "space-after-keywords": [2, "always"], + "space-before-function-paren": [2, {"anonymous": "always", "named": "always"}], + "space-before-blocks": [0, "always"], + "space-in-brackets": [ + 0, "never", { + "singleValue": true, + "arraysInArrays": false, + "arraysInObjects": false, + "objectsInArrays": true, + "objectsInObjects": true, + "propertyName": false + } + ], + "space-in-parens": [0], + "space-infix-ops": [2], + "space-return-throw-case": [2], + "space-unary-ops": [2, {"words": true, "nonwords": false}], + "spaced-line-comment": [0, "always"], + "strict": [2, "never"], + "use-isnan": [2], + "valid-jsdoc": [0], + "valid-typeof": [2], + "vars-on-top": [0], + "wrap-iife": [2], + "wrap-regex": [2], + "yoda": [2, "never", {"exceptRange": true}], + "react/jsx-boolean-value": 2, + "react/jsx-no-undef": 2, + "react/jsx-sort-props": 0, + "react/jsx-sort-prop-types": 0, + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/no-did-mount-set-state": 0, + "react/no-did-update-set-state": 2, + "react/no-multi-comp": 2, + "react/no-unknown-property": 1, + "react/prop-types": 1, + "react/react-in-jsx-scope": 2, + "react/self-closing-comp": 2, + "jsx-quotes": 2, + }, + "plugins": [ + "react" + ], "global": { - "React": true + "React": true } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8c2691b..3f3468e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ + + +## Contents + +- [WeVoteUSA Web App Change Log](#wevoteusa-web-app-change-log) + + + #WeVoteUSA Web App Change Log All notable changes to this project will be documented here. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d17a3931c..f44fad658 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,18 @@ + + +## Contents + +- [Contributing to wevote/WebApp](#contributing-to-wevotewebapp) + - [Pull Requests](#pull-requests) + - [Setting up your repository for work](#setting-up-your-repository-for-work) + - [Some useful tips and tricks](#some-useful-tips-and-tricks) + - [Git completion](#git-completion) + - [Prompt](#prompt) + - [How to get a change from someone’s repository into your repo before it’s pushed to master](#how-to-get-a-change-from-someone%E2%80%99s-repository-into-your-repo-before-it%E2%80%99s-pushed-to-master) + - [How to get rid of garbage files that shouldn’t be in git](#how-to-get-rid-of-garbage-files-that-shouldn%E2%80%99t-be-in-git) + + + # Contributing to wevote/WebApp Thank you for your interest in the We Vote WebApp project. Please let us know if we can help you get started. diff --git a/README.md b/README.md index 5f58d0d8a..3abcf1b9e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ + + +## Contents + +- [README for We Vote WebApp](#readme-for-we-vote-webapp) + - [Contributing](#contributing) + - [Install WeVoteServer First](#install-wevoteserver-first) + - [Install nodeenv ("Node Env")](#install-nodeenv-node-env) + - [Clone https://github.com/wevote/WebApp](#clone-httpsgithubcomwevotewebapp) + - [Install and start web application](#install-and-start-web-application) + - [After Installation: Working with WebApp Day-to-Day](#after-installation-working-with-webapp-day-to-day) + - [Versions](#versions) + + + # README for We Vote WebApp [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) | @@ -10,6 +25,9 @@ social way to interact with ballot data. You can see our current wireframe mockup for a San Francisco ballot here: http://start.wevoteusa.org/ +## Contributing +Please read our [Contributing guidelines](docs/contributing/index.md) before you start contributing to the project. + ## Install WeVoteServer First In order to get the data the WebApp needs, please [install WeVoteServer](https://github.com/wevote/WeVoteServer/blob/master/README_API_INSTALL.md) @@ -73,7 +91,6 @@ You should be able to visit WebApp here: [Read about working with WebApp on a daily basis](README_WORKING_WITH_WEB_APP.md) -## SemVer +## Versions -We follow [SemVer](http://semver.org/) for our releases. Please read if you plan -to tag for any releases. +Please read about how we version the app vs releases - [versions vs releases](docs/contributing/versions/index.md) diff --git a/README_WORKING_WITH_WEB_APP.md b/README_WORKING_WITH_WEB_APP.md index 575be8c64..0024c6274 100644 --- a/README_WORKING_WITH_WEB_APP.md +++ b/README_WORKING_WITH_WEB_APP.md @@ -1,3 +1,13 @@ + + +## Contents + +- [Working with WebApp](#working-with-webapp) + - [Coding Standards](#coding-standards) + - [Checking In Code](#checking-in-code) + + + # Working with WebApp If you are returning to work on WebApp and other developers have made changes, follow these steps. diff --git a/__tests__/Application-test.js b/__tests__/Application-test.js deleted file mode 100644 index b1b1963c2..000000000 --- a/__tests__/Application-test.js +++ /dev/null @@ -1,18 +0,0 @@ -jest.dontMock('../src/js/Application'); - -import React from 'react'; -import ReactDOM from 'react-dom'; -import TestUtils from 'react-addons-test-utils'; - -const Application = require('../src/js/Application'); -const MoreMenu = require('../src/js/components/MoreMenu'); - -describe('Application ', () => { - - xit('should have a list for sign in', () => { - - }); - -}); - - diff --git a/docs/contributing/index.md b/docs/contributing/index.md new file mode 100644 index 000000000..73f539d96 --- /dev/null +++ b/docs/contributing/index.md @@ -0,0 +1,108 @@ +# Contributing + + +## Contents + +- [Reporting bugs](#reporting-bugs) + - [Example](#example) +- [Getting Started](#getting-started) + - [Clone the repo](#clone-the-repo) + - [If there's no issue, please create one](#if-theres-no-issue-please-create-one) + - [Let us Know you're working on the issue](#let-us-know-youre-working-on-the-issue) + - [Create a feature branch:](#create-a-feature-branch) + - [Make your changes and commit:](#make-your-changes-and-commit) + - [Create a Pull Request](#create-a-pull-request) + - [PR Merge Exception](#pr-merge-exception) + - [PR Hints](#pr-hints) + - [For large changes spanning many commits / PRs](#for-large-changes-spanning-many-commits--prs) + + +- [Versions: Release Names vs Version Numbers](versions/index.md) + +## Reporting bugs + +Bug reports should contain the following information: + +* Summary: A brief description. +* Steps to reproduce: How did you encounter the bug? Instructions to reproduce it. +* Expected behavior: How did you expect it to behave? +* Actual behavior: How did it actually behave? +* Screenshot or animated gif: If possible, attach visual documentation of the bug. +* References: Links to any related tickets or information sources. + +### Example + +Here's a [real issue](https://github.com/woothemes/woocommerce/issues/8563#issue-94518347) to demonstrate. + + +## Getting Started + +### Clone the repo + +* Click the GitHub fork button to create your own fork +* Clone your fork of the repo to your dev system + +``` +git clone git@github.com:pertrai1/wevote.git +``` + +### If there's no issue, please create one + + +### Let us Know you're working on the issue + +If you're actively working on an issue, please comment in the issue thread stating that you're working on a fix, or (if you're an official contributor) assign it to yourself. + +This way, others will know they shouldn't try to work on a fix at the same time. + + +### Create a feature branch: + +``` +git checkout -b +``` + +### Make your changes and commit: + +* Make sure you comply with the [.editorconfig](http://editorconfig.org/) + +``` +git commit -m '[Issue #] ' +``` + +### Create a Pull Request + +Please don't merge your own changes. Create a pull request so others can review the changes. + +**Push changes:** + +``` +git push origin +``` + +* Open your repository fork on GitHub +* You should see a button to create a pull request - Press it +* Consider mentioning a contributor in your pull request comments to alert them that it's available for review +* **Wait for the reviewer to approve and merge the request** + +### PR Merge Exception + +* Minor documentation grammar/spelling fixes (code example changes should be reviewed) + + +### PR Hints + +Reference the issue number in your commit message e.g.: + +``` +$ git commit -m '[#5] Make sure to follow the PR process for contributions' +``` + +#### For large changes spanning many commits / PRs + +* Create a meta-issue with a bullet list using the `* [ ] item` markdown syntax. +* Create issues for each bullet point +* Link to the meta-issue from each bullet point issue +* Check off the bullet list as items get completed + +Linking from the bullet point issues to the meta issue will create a list of issues with status indicators in the issue comments stream, which will give us a quick visual reference to see what's done and what still needs doing. diff --git a/docs/contributing/versions/index.md b/docs/contributing/versions/index.md new file mode 100644 index 000000000..3838f280f --- /dev/null +++ b/docs/contributing/versions/index.md @@ -0,0 +1,84 @@ +# Versions: Release Names vs Version Numbers + + +## Contents + +- [What?](#what) +- [Why?](#why) +- [Details](#details) + - [Release Names (AKA code names)](#release-names-aka-code-names) + - [MVP](#mvp) + - [Version Numbers](#version-numbers) + - [Breaking.Feature.Fix](#breakingfeaturefix) + - [Breaking](#breaking) + - [Feature](#feature) + - [Fix](#fix) +- [Examples](#examples) + + + +## What? + +Version numbers are **only** there to communicate the nature of a change: **Breaking.Feature.Fix**. + +Human names are there to communicate, "Hey everybody, we have a new release! Here are the new features!" + +## Why? + +Our releases and versions are separate concepts because the need to communicate new stable release information and the need to inform developers about the nature of changes (breaking, new features, or fixes/security patches) are two separate concerns which advance on separate timetables. + +The conflating of version numbers and public releases has led to a big problem in the software development community. Developers tend to break semantic version numbering, for example, resisting the need to advance the breaking (major) version number because they're not yet ready to release their mvp (which many developers think of as 1.0). + +In other words, we need two separate ways of tracking changes: + +* One for people & public announcements (names). +* One for resolving version conflict problems (numbers). + +## Details + +### Release Names (AKA code names) + +Our major releases have code-names instead of version numbers. The current release is identified by the "latest" tag. The first version is "mvp". After that we pick a theme, and work through the alphabet from A to Z. + +When talking about release versions, we don't say "version Arty" we say "the newest version was released today, code named 'Arty'". After that, we just refer to it as "Arty" or "latest version". More recognizable codename examples include "Windows Vista" or "OS X Yosemite". + + +#### MVP + +MVP stands for "Minimum **Valuable** Product" (a better version of the common "Minimum Viable Product"). The minimum number of features to make the product valuable to users. + +![mvp](https://cloud.githubusercontent.com/assets/364727/8585378/4222fd84-259e-11e5-804c-33ec952ca88d.png) + + +### Version Numbers + +[Semver](http://semver.org), except the version roles have the semantic names, "Breaking.Feature.Fix" instead of "Major.Minor.Patch". + + +#### Breaking.Feature.Fix + +We don't decide what the version will be. The API changes decide. Version numbers are for computers, not people. Release names are for people. + +##### Breaking + +Any breaking change, no matter how small increments the Breaking version number. Incrementing the Breaking version number has absolutely no relationship with issuing a release. + +##### Feature + +When any new feature is added. This could be as small as a new public property, or as large as a new module contract being exposed. + +##### Fix + +When a documented feature does not behave as documented, or when a security issue is discovered and fixed without altering documented behavior. + + + +## Examples + +If it's time to write a blog post to inform the community about new features or important changes, we find the version we want to publicize, tag it "latest", give it a human-readable name, (i.e. "MVP" or "Art Nouveau" in the case of the [JSHomes API](https://github.com/jshomes/jshomes-platform-api/blob/master/README.md#jshomes-api-)). + +That human readable release name **does not replace semver**. "MVP" might correspond to `v1.6.23` or `v2.2.5` -- the point is, **the numbered version has nothing to do with the named release**. + +The numbered version is there so npm and developers can tell whether or not a new version is a breaking change, an added feature change, or a bug / security fix. + + diff --git a/package.json b/package.json index c3f29a69a..7e2847404 100644 --- a/package.json +++ b/package.json @@ -22,17 +22,20 @@ "devDependencies": { "autoprefixer-loader": "^3.1.0", "babel-eslint": "^4.1.8", - "babel-jest": "*", + "babel-plugin-object-assign": "^1.2.1", "babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-preset-stage-0": "^6.3.13", "babelify": "^7.2.0", + "blue-tape": "^0.1.10", "bootstrap": "^3.3.6", "browser-sync": "^2.11.1", "browserify": "^13.0.0", "classnames": "^2.2.1", "del": "^2.2.0", + "dependency-check": "^2.5.0", + "doctoc": "^0.14.2", "eslint": "^1.10.2", "eslint-config-airbnb": "^4.0.0", "eslint-plugin-react": "^3.16.1", @@ -44,10 +47,10 @@ "gulp-cli": "^1.2.0", "gulp-sass": "^2.1.1", "history": "^1.13.1", - "jest-cli": "*", "jscs": "^2.8.0", "moment": "^2.11.2", "moment-timezone": "^0.5.0", + "node-libs-browser": "^0.5.2", "react-addons-linked-state-mixin": "^0.14.3", "react-addons-test-utils": "~0.14.0", "react-bootstrap": "^0.28.1", @@ -66,21 +69,11 @@ }, "repository": "https://github.com/wevote/webapp.git", "scripts": { - "lint": "eslint src && jscs src", - "test": "eslint src && jest", - "start": "gulp" - }, - "jest": { - "moduleFileExtensions": [ - "js", - "jsx" - ], - "scriptPreprocessor": "./node_modules/babel-jest", - "unmockedModulePathPatterns": [ - "./node_modules/react", - "./node_modules/react-dom", - "./node_modules/react-addons-test-utils", - "./node_modules/fbjs" - ] + "lint": "eslint src", + "test": "eslint src", + "start": "gulp", + "deps": "npm run deps:missing && npm run deps:extra", + "deps:missing": "dependency-check package.json", + "deps:extra": "dependency-check package.json --extra --no-dev --ignore", "build:doc": "doctoc --github --title \"## Contents\" ./" } } From 5413fa9b17d28d6bde7c85f99ee1e461659f640c Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 16 Feb 2016 14:23:54 -0500 Subject: [PATCH 31/92] merge conflict resolve --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f4a187979..9d3853799 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ # README for We Vote WebApp -<<<<<<< HEAD [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) | This WebApp repository contains a Node/React/Flux Javascript application. Using data from From 88e5391088a91c82ca5277face7dd1ce14c3dc39 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 16 Feb 2016 17:37:06 -0500 Subject: [PATCH 32/92] test --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index 332ea630d..19087bb19 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,5 @@ // start the express server +// const express = require('express'); const app = express(); From f14647dd0d8a76e4e4105e670c31d996f73c1e30 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 16 Feb 2016 17:50:45 -0500 Subject: [PATCH 33/92] local changes --- Gulpfile.js | 5 +- src/js/components/Header.jsx | 5 +- src/js/index.js | 21 +++++-- src/js/routes/Ballot/Ballot.jsx | 2 +- src/js/routes/Settings/Location.jsx | 13 +++- src/js/stores/BallotStore.js | 39 ++++++------ src/js/stores/VoterStore.js | 98 ++++++++++++++++++++--------- src/js/utils/service.js | 75 +++++++++++++++++----- 8 files changed, 180 insertions(+), 78 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index 157ae2ebf..9ce6e51be 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -27,7 +27,8 @@ gulp.task('browserify', function () { gulp.task('server', function () { browserSync.init({ - proxy: 'localhost:3003' + proxy: 'localhost:3003', + open: false }); }) @@ -37,7 +38,7 @@ gulp.task('sass', function () { .pipe(sass()) .pipe(gulp.dest('./build/css')) .pipe(browserSync.stream()); -}) +}) gulp.task('clean:build', function () { return del.sync(['./build/**']) diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 30e90617c..66de3ad43 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -11,11 +11,14 @@ export default class Header extends Component { } componentDidMount() { - VoterStore.getLocation( location => this.setState({ location })) + VoterStore.getLocation( (err, location) => { + this.setState({ location }) + }) } render () { var {location} = this.state; + return (
      diff --git a/src/js/index.js b/src/js/index.js index 54a6c72e9..867500b0f 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -2,21 +2,30 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { createHistory } from 'history'; import Root from './Root'; - -import { voterBallotItemsRetrieveFromGoogleCivic } from './utils/service'; import VoterStore from './stores/VoterStore'; -console.log('Entering WebApp/src/js/index.js'); - // polyfill if (!Object.assign) Object.assign = React.__spread; -VoterStore.getDeviceId( (firstVisit, id) => +VoterStore.getDeviceId( (err, firstVisit, id) => { + if (err) console.error(err); + + VoterStore.getLocation( (err, location) => { + if (err) console.error(err); + + console.log(location); + render(firstVisit); + + }); + +}); + +function render(firstVisit) { ReactDOM.render( , document.getElementById('app') ) -); +} //console.log("index.js: About to initialize VoterStore"); //VoterStore.initialize((voter_object) => { diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 7d3245a41..a7cf76528 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -15,7 +15,7 @@ export default class Ballot extends Component { } componentDidMount () { - + // BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) } render () { diff --git a/src/js/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index 3d2a58518..808e35964 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -10,7 +10,9 @@ export default class Location extends Component { } componentDidMount() { - VoterStore.getLocation( location => this.setState({ location })) + VoterStore.getLocation( location => { + this.setState({ location }); + }) } updateLocation (e) { @@ -20,8 +22,13 @@ export default class Location extends Component { } saveLocation (e) { - VoterStore.saveLocation ( this.state.location ); - location.href="/ballot" + var { location } = this.state; + VoterStore.saveLocation ( location, (err, location) => { + if (err) return console.error(err); + + window.location.href="/ballot" + + }); } render() { diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 0eda655bb..7037692c7 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -12,6 +12,10 @@ let _google_civic_election_id = null; const MEASURE = 'MEASURE'; +function defaultSuccess (res) { + console.warn(res); +} + function addItemsToBallotStore (ballot_item_list) { ballot_item_list.forEach( ballot_item => { _ballot_store[ballot_item.we_vote_id] = shallowClone(ballot_item); @@ -28,24 +32,19 @@ const BallotAPIWorker = { voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { return get({ endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', - query: { text_for_map_search }, success + query: { text_for_map_search }, success: success || defaultSuccess }); }, candidatesRetrieve: function (office_we_vote_id, success ) { - return get({ - endpoint: 'candidatesRetrieve', - query: { office_we_vote_id }, - success - }); + return get({ endpoint: 'candidatesRetrieve', query: { office_we_vote_id }, + success: success || defaultSuccess }); }, // get the ballot items voterBallotItemsRetrieve: function ( success ) { - return get({ - endpoint: 'voterBallotItemsRetrieve', - success - }); + return get({ endpoint: 'voterBallotItemsRetrieve', + success: success || defaultSuccess }); }, positionOpposeCountForBallotItem: function (we_vote_id, success ) { @@ -54,7 +53,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -65,7 +64,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -75,7 +74,7 @@ const BallotAPIWorker = { query: { ballot_item_we_vote_id: ballot_item_we_vote_id, kind_of_ballot_item: _ballot_store[ballot_item_we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -85,7 +84,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -95,7 +94,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -105,7 +104,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -116,7 +115,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -127,7 +126,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -138,7 +137,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); }, @@ -149,7 +148,7 @@ const BallotAPIWorker = { query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item - }, success + }, success: success || defaultSuccess }); } }; diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 180a584d3..05acc3e99 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -1,5 +1,7 @@ import assign from 'object-assign'; -import { deviceIdGenerate, voterLocationRetrieveFromIP } from '../utils/service'; +import { + $ajax, $post, deviceIdGenerate, voterLocationRetrieveFromIP +} from '../utils/service'; import { createStore } from '../utils/createStore'; import AppDispatcher from '../dispatcher/AppDispatcher'; @@ -17,18 +19,26 @@ function error (err) { console.error('WVError:', err.message); } -function setVoterDeviceId (id) { +function setDeviceId (id) { _voter_device_id = id; cookies.setItem('voter_device_id', id, Infinity) } -function setVoterLocation (location) { +function setLocation (location) { _location = location; cookies.setItem('location', location, Infinity); } +function _getLocation () { + return _location || null; +} + const VoterStore = createStore({ + hasDeviceId: function () { + if (_voter_device_id) return true; + else return false; + }, /** * initialize the voter store with data, if no data * and callback with the voter items @@ -38,15 +48,62 @@ const VoterStore = createStore({ if (!callback || typeof callback !== 'function') throw new Error('VoterStore: getDeviceId must be called with callback'); - if ( !_voter_device_id ) deviceIdGenerate() - .then ( res => { - var {voter_device_id: id} = res; - setVoterDeviceId(id); - callback(true, id); - }) - .catch( error ); + if (this.hasDeviceId()) callback(null, false, _voter_device_id); + + else $ajax({ + endpoint: "deviceIdGenerate", + success: (res) => { + var { voter_device_id: id } = res; - else callback( false, _voter_device_id ); + setDeviceId(id); + callback(null, true, id); + }, + error: (err) => { + callback(err, true, null); + } + }); + }, + + /** + * get the Voters location + * @return {String} location + */ + getLocation: function (callback) { + if (_location) callback(null, _location) + else $ajax({ + endpoint: "voterAddressRetrieve", + success: (res) => { + var { text_for_map_search: location } = res; + + setLocation(location); + callback(null, location); + }, + error: (err) => callback(err) + }); + + }, + + /** + * save the voter's location to override API IP guess + * @param {String} location + * @param {Function} callback function that accepts (err,location) + */ + saveLocation: function (location, callback) { + if (typeof location !== "string") throw new Error('missing location to save'); + if (callback instanceof Function === false) throw new Error('missing callback function'); + + $ajax({ + type: 'POST', + data: { text_for_map_search: location }, + endpoint: 'voterAddressSave', + success: (res) => { + var { text_for_map_search: location } = res; + + setLocation(location); + callback(null, location); + }, + error: (err) => callback(err, null) + }) }, signInStatus: function (callback) { @@ -98,25 +155,6 @@ const VoterStore = createStore({ VoterActions.ChangeLocation(location); return location; - }, - - saveLocation: function (location) { - cookies.setItem('location', location); - VoterActions.ChangeLocation(location); - }, - - /** - * get the Voters location - * @return {String} location - */ - getLocation: function (callback) { - if (_location) callback(_location) - else voterLocationRetrieveFromIP() - .then( response => { - var { voter_location: location } = response; - setVoterLocation(location); - callback(location); - }); } }); diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 9db83fb2f..e102306cf 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -6,36 +6,53 @@ 'use strict'; const DEBUG = false; - -import assign from 'object-assign'; -import * as request from 'superagent'; - const url = require('url'); -const web_app_config = require('../config'); + +const assign = require('object-assign'); +const ajax = require('../vendor/jquery.js').ajax; +const webAppConfig = require('../config'); +const cookies = require('./cookies'); const defaults = { dataType: 'json', - WE_VOTE_SERVER_API_ROOT_URL: web_app_config.WE_VOTE_SERVER_API_ROOT_URL, - query: {} + baseUrl: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, + url: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, + query: {}, + type: 'GET', + data: { + voter_device_id: cookies.getItem('voter_device_id') + }, + success: (res) => console.warn('Success function not defined:', res), + error: (err) => console.error(err.message) }; +export function $ajax (options) { + if (!options.endpoint) throw new Error('$ajax missing endpoint option'); + + options.data = assign({}, defaults.data, options.data || {}); + options.crossDomain = true; + options.success = options.success || defaults.success; + options.error = options.error || defaults.error; + options.url = url.resolve(defaults.baseUrl, options.endpoint); + + return ajax(options); +} + export function get (options) { var opts = assign(defaults, options); - opts.WE_VOTE_SERVER_API_ROOT_URL = url.resolve(opts.WE_VOTE_SERVER_API_ROOT_URL, opts.endpoint); - return new Promise( (resolve, reject) => request - .get(opts.WE_VOTE_SERVER_API_ROOT_URL) + opts.url = url.resolve(opts.baseUrl, opts.endpoint); + + return new Promise( (resolve, reject) => new request.Request('GET', opts.url) .accept(opts.dataType) .query(opts.query) .withCredentials() .end((err, res) => { - if (err || !res.body.status) { + if (err) { if (opts.error instanceof Function === true) opts.error(err || res.body); - else - console.error(err || res.body); - reject(err || res.body); + reject(err); } else { if (opts.success instanceof Function === true) @@ -46,9 +63,37 @@ export function get (options) { resolve(res.body); } }) - ); + ) } +export function $post (options) { + var opts = assign(defaults, options); + opts.url = url.resolve(opts.baseUrl, opts.endpoint) + '/'; + + return new Promise( (resolve, reject) => new request.Request('POST', opts.url) + .accept(opts.dataType) + .withCredentials() + .send(opts.send) + .end((err, res) => { + if (err) { + if (opts.error instanceof Function === true) + opts.error(err || res.body); + + reject(err); + } + else { + if (opts.success instanceof Function === true) + opts.success(res.body); + else if (DEBUG) + console.warn(res.body); + + resolve(res.body); + } + }) + ) +} + + export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { return get({ endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', From 2dc1124e921af67710535c392963f8a03d4562a0 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 16 Feb 2016 18:29:28 -0500 Subject: [PATCH 34/92] eslint modifications to loosen build test --- .eslintignore | 1 + .eslintrc | 58 +++++++++++++++++----------------- src/js/.eslintrc | 49 ---------------------------- src/js/stores/FacebookStore.js | 24 ++++---------- src/js/utils/service.js | 6 ++-- 5 files changed, 39 insertions(+), 99 deletions(-) delete mode 100644 src/js/.eslintrc diff --git a/.eslintignore b/.eslintignore index e69de29bb..7dc7fbf62 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1 @@ +src/js/vendor/*.js diff --git a/.eslintrc b/.eslintrc index 2f9674cb8..915455c5b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,23 +37,23 @@ "brace-style": [2, "1tbs", {"allowSingleLine": true}], "camelcase": [0], "comma-dangle": [0], - "comma-spacing": [2], + "comma-spacing": [1], "comma-style": [2, "last"], "complexity": [0, 11], - "consistent-return": [2], + "consistent-return": [1], "consistent-this": [0, "that"], - "curly": [2, "multi-line"], - "default-case": [2], + "curly": [0, "multi-line"], + "default-case": [1], "dot-notation": [2, {"allowKeywords": true}], - "eol-last": [2], - "eqeqeq": [2], + "eol-last": [1], + "eqeqeq": [1], "func-names": [0], "func-style": [0, "declaration"], "generator-star-spacing": [2, "after"], "guard-for-in": [0], "handle-callback-err": [0], - "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "quotes": [2, "single", "avoid-escape"], + "key-spacing": [1, {"beforeColon": false, "afterColon": true}], + "quotes": [1, "double", "avoid-escape"], "max-depth": [0, 4], "max-len": [0, 80, 4], "max-nested-callbacks": [0, 2], @@ -102,10 +102,10 @@ "no-labels": [2], "no-lone-blocks": [2], "no-lonely-if": [2], - "no-loop-func": [2], + "no-loop-func": [1], "no-mixed-requires": [0, false], "no-mixed-spaces-and-tabs": [2, false], - "no-multi-spaces": [2], + "no-multi-spaces": [1], "no-multi-str": [2], "no-multiple-empty-lines": [2, {"max": 2}], "no-native-reassign": [1], @@ -119,51 +119,51 @@ "no-obj-calls": [2], "no-octal": [2], "no-octal-escape": [2], - "no-param-reassign": [2], + "no-param-reassign": [1], "no-path-concat": [0], "no-plusplus": [0], "no-process-env": [0], "no-process-exit": [2], "no-proto": [2], - "no-redeclare": [2], + "no-redeclare": [1], "no-regex-spaces": [2], "no-reserved-keys": [0], "no-restricted-modules": [0], "no-return-assign": [2], "no-script-url": [2], "no-self-compare": [0], - "no-sequences": [2], - "no-shadow": [2], + "no-sequences": [1], + "no-shadow": [1], "no-shadow-restricted-names": [2], - "no-spaced-func": [2], + "no-spaced-func": [1], "no-sparse-arrays": [2], "no-sync": [0], "no-ternary": [0], "no-throw-literal": [2], - "no-trailing-spaces": [2], - "no-undef": [2], + "no-trailing-spaces": [1], + "no-undef": [1], "no-undef-init": [2], "no-undefined": [0], - "no-underscore-dangle": [2], + "no-underscore-dangle": [0], "no-unreachable": [2], - "no-unused-expressions": [2], + "no-unused-expressions": [1], "no-unused-vars": [1, {"vars": "all", "args": "after-used"}], - "no-use-before-define": [2], + "no-use-before-define": [1], "no-void": [0], "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}], "no-with": [2], - "no-extra-parens": [2], - "one-var": [0], + "no-extra-parens": [1], + "one-var": [1, "never"], "operator-assignment": [0, "always"], - "operator-linebreak": [2, "after"], + "operator-linebreak": [1, "after"], "padded-blocks": [0], "quote-props": [0], "radix": [0], - "semi": [2], + "semi": [1], "semi-spacing": [2, {"before": false, "after": true}], "sort-vars": [0], "space-after-keywords": [2, "always"], - "space-before-function-paren": [2, {"anonymous": "always", "named": "always"}], + "space-before-function-paren": [1, {"anonymous": "always", "named": "always"}], "space-before-blocks": [0, "always"], "space-in-brackets": [ 0, "never", { @@ -176,17 +176,17 @@ } ], "space-in-parens": [0], - "space-infix-ops": [2], + "space-infix-ops": [1], "space-return-throw-case": [2], - "space-unary-ops": [2, {"words": true, "nonwords": false}], + "space-unary-ops": [0, {"words": true, "nonwords": false}], "spaced-line-comment": [0, "always"], - "strict": [2, "never"], + "strict": [0, "never"], "use-isnan": [2], "valid-jsdoc": [0], "valid-typeof": [2], "vars-on-top": [0], "wrap-iife": [2], - "wrap-regex": [2], + "wrap-regex": [1], "yoda": [2, "never", {"exceptRange": true}], "react/jsx-boolean-value": 2, "react/jsx-no-undef": 2, diff --git a/src/js/.eslintrc b/src/js/.eslintrc deleted file mode 100644 index 09a64bebe..000000000 --- a/src/js/.eslintrc +++ /dev/null @@ -1,49 +0,0 @@ -{ - "parser": "babel-eslint", - "env": { - "node": true, - "browser": true - }, - "ecmaFeatures": { - "arrowFunctions": true, - "blockBindings": true, - "classes": true, - "defaultParams": true, - "destructuring": true, - "forOf": true, - "modules": true, - "objectLiteralComputedProperties": true, - "objectLiteralShorthandMethods": true, - "objectLiteralShorthandProperties": true, - "spread": true, - "superInFunctions": true, - "templateStrings": true, - "unicodeCodePointEscapes": true, - "jsx": true - }, - "rules": { - "react/jsx-boolean-value": 2, - "react/jsx-quotes": 2, - "react/jsx-no-undef": 2, - "react/jsx-sort-props": 0, - "react/jsx-sort-prop-types": 0, - "react/jsx-uses-react": 2, - "react/jsx-uses-vars": 2, - "react/no-did-mount-set-state": 0, - "react/no-did-update-set-state": 2, - "react/no-multi-comp": 2, - "react/no-unknown-property": 1, - "react/prop-types": 1, - "react/react-in-jsx-scope": 2, - "react/self-closing-comp": 2, - "react/wrap-multilines": 0, - "jsx-quotes": 1, - "quotes": 0 - }, - "plugins": [ - "react" - ], - "globals": { - "React": true - } -} diff --git a/src/js/stores/FacebookStore.js b/src/js/stores/FacebookStore.js index ad7feba0b..655b39288 100644 --- a/src/js/stores/FacebookStore.js +++ b/src/js/stores/FacebookStore.js @@ -133,6 +133,13 @@ const FacebookAPIWorker = { } }; +function sleep (milliseconds) { + var start = new Date().getTime(); + for (var i = 0; i < 1e7; i++) { + if (new Date().getTime() - start > milliseconds) break; + } +} + // initialize the store as a singleton const facebookStore = new FacebookStore(); @@ -146,15 +153,6 @@ facebookStore.dispatchToken = FacebookDispatcher.register((action) => { facebookStore.setFacebookAuthData(action.data); facebookStore.saveFacebookAuthData(); - function sleep(milliseconds) { - var start = new Date().getTime(); - for (var i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds){ - break; - } - } - } - sleep(3000); console.log("FACEBOOK_LOGGED_IN: Trying to retrieve fresh voter data"); var voter = VoterStore.getVoterObject(); @@ -176,14 +174,6 @@ facebookStore.dispatchToken = FacebookDispatcher.register((action) => { console.log("FACEBOOK_SIGN_IN_DISCONNECT"); facebookStore.disconnectFromFacebook(); - function sleep(milliseconds) { - var start = new Date().getTime(); - for (var i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds){ - break; - } - } - } sleep(3000); console.log("FACEBOOK_SIGN_IN_DISCONNECT: Trying to retrieve fresh voter data"); diff --git a/src/js/utils/service.js b/src/js/utils/service.js index e102306cf..8f224de2c 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -53,8 +53,7 @@ export function get (options) { opts.error(err || res.body); reject(err); - } - else { + } else { if (opts.success instanceof Function === true) opts.success(res.body); else if (DEBUG) @@ -80,8 +79,7 @@ export function $post (options) { opts.error(err || res.body); reject(err); - } - else { + } else { if (opts.success instanceof Function === true) opts.success(res.body); else if (DEBUG) From 0b345f909899b2dc4a5efe149a024337d0827647 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Tue, 16 Feb 2016 18:35:02 -0800 Subject: [PATCH 35/92] Make changes based on code review --- src/js/components/Ballot/PositionItem.jsx | 44 ++-- src/js/routes/Ballot/Candidate.jsx | 244 +++++++++++++++------- src/js/stores/PositionStore.js | 15 ++ 3 files changed, 208 insertions(+), 95 deletions(-) diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index fee773869..536b1e662 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -33,29 +33,31 @@ export default class PositionItem extends Component { var supportText = position.is_oppose ? "Opposes" : "Supports"; return (
      + {/* One organization's Position on this Candidate */}
    • -
      -
      - - +
      +
      + + + +
      +
      +

      + + { this.props.speaker_label }
      {/* TODO icon-org-placeholder */} -

      -
      -

      - - { this.props.speaker_label } - -

      -

      {supportText} Yesterday at 7:18 PM

      -
      -
      -
      - {position.statement_text} -
      -
      - 23 Likes
      -
    • + +

      supports Yesterday at 7:18 PM

      +
      +
      +
      + {position.statement_text} +
      + {/* Likes coming in a later version +
      + 23 Likes
      + */} +
      ); } diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index f541c992f..ecb1aeb6a 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -33,17 +33,13 @@ export default class Candidate extends Component { } render() { - // if (Object.keys(this.state.candidate).length === 0){ - // this.props.history.replace('/ballot'); - // }; var candidate = this.state.candidate; var we_vote_id = this.props.params.we_vote_id; if (!candidate.we_vote_id){ return (
      ); }; + // var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); - // if (Object.keys(candidate).length === 0){ - // this.props.history.replace('/ballot');} var support_item; if (this.props.support_on) { @@ -60,80 +56,180 @@ export default class Candidate extends Component { } return ( -
      - {/* -
      -
      - - < Back to My Ballot - -
      -
      - - - - More Opinions - -
      -
      - */} - - -
      -
      - - { - candidate.candidate_photo_url ? - candidate-photo : - - } -
      -
      -

      - - { candidate.ballot_item_display_name } +
      +
      + {/* +
      +
      + + < Back to My Ballot + +
      +
      + + + + More Opinions -

      -
      - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
      - Courtesy of Ballotpedia.org */} +
      + + */} + + +
      +
      + + { + candidate.candidate_photo_url ? + candidate-photo : + + } +
      +
      +

      + + { candidate.ballot_item_display_name } + +

      +

      Running for { candidate.office_display_name }

      -
      -
      { candidate.office_display_name }
      - + +
      + {/* Post privately box */} + {/* +
        +
      • +
        + + +
        +
      • +
      + */} + +
      -
      -
      -
        -
      • -
        - - -
        -
      • -
      - - -
      -
      - ); +
      + + ); } + + // render() { + // + // var candidate = this.state.candidate; + // var we_vote_id = this.props.params.we_vote_id; + // if (!candidate.we_vote_id){ + // return (
      ); + // }; + // + // + // + // var support_item; + // if (this.props.support_on) { + // support_item = 7 ; + // } else { + // support_item = 7 ; + // } + // + // var oppose_item; + // if (this.props.oppose_on) { + // oppose_item = 3 ; + // } else { + // oppose_item = 3 ; + // } + // + // return ( + //
      + // {/* + //
      + //
      + // + // < Back to My Ballot + // + //
      + //
      + // + // + // + // More Opinions + // + //
      + //
      + // */} + // + // + //
      + //
      + // + // { + // candidate.candidate_photo_url ? + // candidate-photo : + // + // } + //
      + //
      + //

      + // + // { candidate.ballot_item_display_name } + // + //

      + //
      + // {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. + // Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
      + // Courtesy of Ballotpedia.org */} + //
      + //
      + //
      + //
      { candidate.office_display_name }
      + // + //
      + //
      + //
      + //
        + //
      • + //
        + // + // + //
        + //
      • + //
      + // + // + //
      + // + //
      + // ); + + // } } diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js index 4057f81af..db4826c1d 100644 --- a/src/js/stores/PositionStore.js +++ b/src/js/stores/PositionStore.js @@ -6,6 +6,7 @@ const AppDispatcher = require('../dispatcher/AppDispatcher'); const PositionConstants = require('../constants/PositionConstants'); const PositionActions = require('../actions/PositionActions'); +const POSITION_CHANGE_EVENT = 'POSITION_CHANGE_EVENT'; var _position_store = {}; // All positions that have been fetched (by we_vote_ids) function printErr (err) { @@ -37,6 +38,20 @@ retrievePositionByWeVoteId: function (we_vote_id){ }); }, +emitChange: function (){ + this.emit(POSITION_CHANGE_EVENT); +}, + +addChangeListener: function(callback) { + // console.log("Change listener added"); + this.on(POSITION_CHANGE_EVENT, callback); + }, + + removeChangeListener: function(callback) { + // console.log("Change listener removed!"); + this.removeListener(POSITION_CHANGE_EVENT, callback); + }, + getLocalPositionByWeVoteId: function (we_vote_id){ return shallowClone(_position_store[we_vote_id]); } From 339f5428ebad120ad3f8edfc60708eeb464a2125 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 17 Feb 2016 00:54:57 -0500 Subject: [PATCH 36/92] add forking documentation to README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c6b7f508..341b5d72c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![WeVoteUS](wevotelogo.png) -[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) | +[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a @@ -13,6 +13,7 @@ http://start.wevoteusa.org/ ## Contributing Please read our [Contributing guidelines](docs/contributing/index.md) before you start contributing to the project. +For best practices, please read [how to fork and create pull requests](CONTRIBUTING.md). ## Install nodeenv ("Node Env") From 7c213a4433a5ec366333fda0d8cd8579748cd027 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Wed, 17 Feb 2016 00:56:45 -0500 Subject: [PATCH 37/92] text position error in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 341b5d72c..04c5c72ef 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ You can see our current wireframe mockup for a San Francisco ballot here: http://start.wevoteusa.org/ ## Contributing -Please read our [Contributing guidelines](docs/contributing/index.md) before you start contributing to the project. +Please read our [Contributing guidelines](docs/contributing/index.md) before you start contributing to the project. For best practices, please read [how to fork and create pull requests](CONTRIBUTING.md). ## Install nodeenv ("Node Env") From 72dfcaf9abc9fad4950205a9b1854dde580b571f Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Wed, 17 Feb 2016 18:14:52 -0500 Subject: [PATCH 38/92] updates to voter store for proper authentication --- .eslintrc | 2 +- server.js | 11 +- src/js/Application.jsx | 48 ++-- src/js/Root.jsx | 205 ++++++++---------- src/js/components/Facebook/FacebookSignIn.jsx | 33 ++- src/js/components/Header.jsx | 49 ++--- src/js/index.js | 110 +++++++--- src/js/routes/Ballot/Ballot.jsx | 42 ++-- src/js/stores/VoterStore.js | 171 ++++++++++----- src/js/utils/object-utils.js | 17 -- src/js/utils/service.js | 92 ++++---- 11 files changed, 412 insertions(+), 368 deletions(-) diff --git a/.eslintrc b/.eslintrc index 915455c5b..d0aaa2b13 100644 --- a/.eslintrc +++ b/.eslintrc @@ -147,7 +147,7 @@ "no-underscore-dangle": [0], "no-unreachable": [2], "no-unused-expressions": [1], - "no-unused-vars": [1, {"vars": "all", "args": "after-used"}], + "no-unused-vars": [1], "no-use-before-define": [1], "no-void": [0], "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}], diff --git a/server.js b/server.js index 19087bb19..f656b02f2 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,5 @@ // start the express server -// -const express = require('express'); +const express = require("express"); const app = express(); const port = 3003; @@ -9,9 +8,9 @@ const opts = { }; app - .use( '/', express.static('build', opts)) - .all( '*', (req, res) => res.sendFile(__dirname + '/build/index.html')) + .use( "/", express.static("build", opts)) + .all( "*", (req, res) => res.sendFile(__dirname + "/build/index.html")) .listen(port, () => - console.log('INFO: '.bold + 'express server started', new Date()) || - console.log('INFO: '.bold + 'Server is at http://localhost:%d', port) + console.log("INFO: " + "express server started", new Date()) || + console.log("INFO: " + "Server is at http://localhost:%d", port) ); diff --git a/src/js/Application.jsx b/src/js/Application.jsx index c6196401c..12fbd90f5 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -1,36 +1,30 @@ import React, { Component, PropTypes } from "react"; -import Navigator from './components/Navigator'; -import MoreMenu from './components/MoreMenu'; -import Header from './components/Header'; -import SubHeader from './components/SubHeader'; -import VoterStore from './stores/VoterStore'; +import Navigator from "./components/Navigator"; +import MoreMenu from "./components/MoreMenu"; +import Header from "./components/Header"; +import SubHeader from "./components/SubHeader"; export default class Application extends Component { static propTypes = { - children: PropTypes.object, - voter: PropTypes.object + children: PropTypes.element, + route: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); - this.state = { - voter: {} - }; + this.state = {}; } - componentDidMount() { - console.log("Application: About to initialize VoterStore"); - VoterStore.signInStatus((voter) => { - //console.log(voter, 'voter is your object') - this.setState({voter}); - }); + componentDidMount () { + // TODO: Figure out if voter is actually signed in... + this.setState({ is_signed_in: false }); } - render() { - var { voter } = this.state; + render () { + var {is_signed_in} = this.state; + var {voter} = this.props.route; - return ( -
      + return
      @@ -44,14 +38,7 @@ export default class Application extends Component {
      - { - voter ? -
      - -
      - : - - } + { is_signed_in ? : }
      { this.props.children } @@ -59,7 +46,6 @@ export default class Application extends Component {
      -
      - ); +
      ; } } diff --git a/src/js/Root.jsx b/src/js/Root.jsx index d7b7d0b83..603d37d68 100644 --- a/src/js/Root.jsx +++ b/src/js/Root.jsx @@ -1,129 +1,102 @@ -import React, { Component, PropTypes } from 'react'; -import { Router, Route, IndexRoute, IndexRedirect } from 'react-router'; +import React from "react"; +import { Route, IndexRoute, IndexRedirect } from "react-router"; // main Application -import Application from './Application'; +import Application from "./Application"; /****************************** ROUTE-COMPONENTS ******************************/ /* Intro */ -import Intro from './routes/Intro/Intro'; -import IntroContests from './routes/Intro/IntroContests'; -import IntroOpinions from './routes/Intro/IntroOpinions'; +import Intro from "./routes/Intro/Intro"; +import IntroContests from "./routes/Intro/IntroContests"; +import IntroOpinions from "./routes/Intro/IntroOpinions"; /* Settings */ -import SettingsDashboard from './routes/Settings/SettingsDashboard'; -import Settings from './routes/Settings/Settings'; -import Location from './routes/Settings/Location'; +import SettingsDashboard from "./routes/Settings/SettingsDashboard"; +import Settings from "./routes/Settings/Settings"; +import Location from "./routes/Settings/Location"; /* Pages that use Ballot Navigation */ -import BallotIndex from './routes/Ballot/BallotIndex'; -import Ballot from './routes/Ballot/Ballot'; -import Candidate from './routes/Ballot/Candidate'; +import BallotIndex from "./routes/Ballot/BallotIndex"; +import Ballot from "./routes/Ballot/Ballot"; +import Candidate from "./routes/Ballot/Candidate"; /* Ballot Off-shoot Pages */ -import Opinions from './routes/Opinions'; +import Opinions from "./routes/Opinions"; /* More */ -import More from './routes/More'; -import About from './routes/More/About'; -import OpinionsFollowed from './routes/More/OpinionsFollowed'; -import SignIn from './routes/More/SignIn'; -import EmailBallot from './routes/More/EmailBallot'; -import Privacy from './routes/More/Privacy'; - - -// import Measure from 'routes/Ballot/Measure'; -// import Opinion from 'routes/Ballot/Opinion'; -import Requests from './routes/Requests'; -import Connect from './routes/Connect'; -import Activity from './routes/Activity'; -import NotFound from './routes/NotFound'; -import AddFriends from './routes/AddFriends'; - - -class Root extends Component { - static propTypes = { - history: PropTypes.object.isRequired, - firstVisit: PropTypes.bool.isRequired, - //voter_object: PropTypes.object - }; - - constructor(props) { - super(props); - } - - render() { - const { history } = this.props; - //var { voter_object } = this.props; - - // Add to - { - /* - * This is the intro section of the application. - * First time visitors should be directed here. - */ - } - - - - - - {/* Settings go in this structure... */} - - - /* Complete path on one line for searching */ - - - {/* Ballot Off-shoot Pages */} - - - - - - - - {/* More Menu Pages */} - - - - - - - - - { - this.props.firstVisit ? - : - } - - - - - - {/* - - - - - - - */} - - - - - - - - // Any route that is not found -> @return NotFound component - - - {/* Routes should not be placed down here */} - - ); - } -}; - -export default Root; +import More from "./routes/More"; +import About from "./routes/More/About"; +import OpinionsFollowed from "./routes/More/OpinionsFollowed"; +import SignIn from "./routes/More/SignIn"; +import EmailBallot from "./routes/More/EmailBallot"; +import Privacy from "./routes/More/Privacy"; + +// import Measure from "routes/Ballot/Measure"; +// import Opinion from "routes/Ballot/Opinion"; +import Requests from "./routes/Requests"; +import Connect from "./routes/Connect"; +import Activity from "./routes/Activity"; +import NotFound from "./routes/NotFound"; +import AddFriends from "./routes/AddFriends"; + +const routes = (firstVisit, voter) => + + { + /* + * This is the intro section of the application. + * First time visitors should be directed here. + */ + } + + + + + + {/* Settings go in this structure... */} + + + /* Complete path on one line for searching */ + + + {/* Ballot Off-shoot Pages */} + + + + + + + + {/* More Menu Pages */} + + + + + + + { firstVisit ? : } + + + + + + {/* + + + + + + + */} + + + + + + + + // Any route that is not found -> @return NotFound component + + ; + + +export default routes; diff --git a/src/js/components/Facebook/FacebookSignIn.jsx b/src/js/components/Facebook/FacebookSignIn.jsx index 0afb782f8..c6d2480ae 100644 --- a/src/js/components/Facebook/FacebookSignIn.jsx +++ b/src/js/components/Facebook/FacebookSignIn.jsx @@ -1,25 +1,20 @@ -import React from 'react'; -import FacebookActionCreators from '../../actions/FacebookActionCreators'; -import VoterStore from '../../stores/VoterStore'; - -const VoterActions = require('../../actions/VoterActions'); +import React from "react"; +import FacebookActionCreators from "../../actions/FacebookActionCreators"; class FacebookSignIn extends React.Component { - constructor(props) { - super(props); - } - render() { - return ( - - Sign in with Facebook - - ); - } + constructor (props) { + super(props); + } + render () { + return + Sign in with Facebook + ; + } - didClickFacebookLoginButton(e) { - console.log("didClickFacebookLoginButton"); - FacebookActionCreators.login(); - } + didClickFacebookLoginButton () { + console.log("didClickFacebookLoginButton"); + FacebookActionCreators.login(); + } } export default FacebookSignIn; diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 66de3ad43..443f5f17d 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -1,42 +1,41 @@ -import React, { Component, PropTypes } from "react"; +import React, { Component } from "react"; import { Link } from "react-router"; import Headroom from "react-headroom"; -import VoterStore from '../stores/VoterStore'; +import VoterStore from "../stores/VoterStore"; export default class Header extends Component { - constructor(props) { + constructor (props) { super(props); this.state = {}; } - componentDidMount() { + componentDidMount () { VoterStore.getLocation( (err, location) => { - this.setState({ location }) - }) + if (err) console.error(err); + this.setState({ location }); + }); } render () { var {location} = this.state; - - return ( -
      - -
      -

      - - + + return
      + +
      +

      + + + + My Ballot +

      +

      - -
      -
      -
      - ); + + + + ; } } diff --git a/src/js/index.js b/src/js/index.js index 867500b0f..67efda2a5 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,38 +1,96 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { createHistory } from 'history'; -import Root from './Root'; -import VoterStore from './stores/VoterStore'; +import React from "react"; +import Router from "react-router"; +import ReactDOM from "react-dom"; +import { createHistory } from "history"; + +import VoterStore from "./stores/VoterStore"; +import routes from "./Root"; // polyfill if (!Object.assign) Object.assign = React.__spread; -VoterStore.getDeviceId( (err, firstVisit, id) => { - if (err) console.error(err); +// wrapping for privacy +(function () { + + function handleVoterError (err) { + console.error("Error initializing voter object", err); + } + + function renderApp (firstVisit, voter) { + ReactDOM.render( + + { routes(firstVisit, voter) } + , document.getElementById("app") + ); + } + + + if (VoterStore.hasDeviceId() && VoterStore.hasVoterId() && VoterStore.hasLocation()) { + + VoterStore.getVoterObject( (err, voter) => { //noop - base case, all cookies are set + if (err) handleVoterError(err); + + renderApp(false, voter); + + }); + + } else if (VoterStore.hasDeviceId() && VoterStore.hasVoterId() && !VoterStore.hasLocation()) { + + VoterStore.getLocation( (err) => { + if (err) handleVoterError(err); + + VoterStore.getVoterObject( (_err, voter) => { + if (_err) handleVoterError(_err); + + renderApp(false, voter); + + }); + + }); + + } else if (VoterStore.hasDeviceId() && !VoterStore.hasVoterId() && !VoterStore.hasLocation()) { + + VoterStore.createVoter( (err) => { + if (err) handleVoterError(err); + + VoterStore.getLocation( (error) => { + if (error) handleVoterError(error); + + VoterStore.getVoterObject( (_err, voter) => { + if (_err) handleVoterError(_err); + + renderApp(true, voter); + + }); + + }); + + }); + + } else { + + VoterStore.getDeviceId( (err) => { + if (err) handleVoterError(err); + + VoterStore.createVoter( (err) => { + if (err) handleVoterError(err); + + VoterStore.getLocation( (err) => { + if (err) handleVoterError(err); - VoterStore.getLocation( (err, location) => { - if (err) console.error(err); + VoterStore.getVoterObject( (err, voter) => { + if (err) handleVoterError(err); - console.log(location); - render(firstVisit); + renderApp(true, voter) - }); + }); -}); + }); -function render(firstVisit) { - ReactDOM.render( - , - document.getElementById('app') - ) -} + }); -//console.log("index.js: About to initialize VoterStore"); -//VoterStore.initialize((voter_object) => { -// ReactDOM.render( -// , -// document.getElementById('app') -// ); -//}); + }); + } +}()) \ No newline at end of file diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index a7cf76528..767bc3b54 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -1,40 +1,36 @@ -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; - -import BallotStore from '../../stores/BallotStore'; -import BallotItem from '../../components/Ballot/BallotItem'; +import React, { Component, PropTypes } from "react"; +import BallotStore from "../../stores/BallotStore"; +import BallotItem from "../../components/Ballot/BallotItem"; export default class Ballot extends Component { static propTypes = { children: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); this.state = {}; } componentDidMount () { - // BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) + BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) } render () { var { ballot_list } = this.state; - return ( -
      - { - ballot_list ? ballot_list - .map( item => - - ) : ( -
      - -

      Loading ... One Moment

      -
      - ) - } -
      - ); + return
      + { + ballot_list ? ballot_list + .map( item => + + ) : ( +
      + +

      Loading ... One Moment

      +
      + ) + } +
      ; } -}; +} diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 05acc3e99..1e250c350 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -1,81 +1,139 @@ -import assign from 'object-assign'; -import { - $ajax, $post, deviceIdGenerate, voterLocationRetrieveFromIP -} from '../utils/service'; -import { createStore } from '../utils/createStore'; +import assign from "object-assign"; +import { $ajax } from "../utils/service"; +import { createStore } from "../utils/createStore"; -import AppDispatcher from '../dispatcher/AppDispatcher'; -import VoterActions from '../actions/VoterActions'; -import VoterConstants from '../constants/VoterConstants'; +import AppDispatcher from "../dispatcher/AppDispatcher"; +import VoterActions from "../actions/VoterActions"; +import VoterConstants from "../constants/VoterConstants"; -const cookies = require('../utils/cookies'); -const CHANGE_EVENT = 'change'; +const cookies = require("../utils/cookies"); -let _voter_device_id = cookies.getItem('voter_device_id'); -let _location = cookies.getItem('location'); +let _voter_device_id = cookies.getItem("voter_device_id"); +let _location = cookies.getItem("location"); +let _voter_id = cookies.getItem("voter_id"); let _voter = {}; -function error (err) { - console.error('WVError:', err.message); +function _setVoterId (id) { + _voter_id = id; + cookies.setItem("voter_id", id, Infinity); } -function setDeviceId (id) { +function _setDeviceId (id) { _voter_device_id = id; - cookies.setItem('voter_device_id', id, Infinity) + cookies.setItem("voter_device_id", id, Infinity); } -function setLocation (location) { +function _setLocation (location) { _location = location; - cookies.setItem('location', location, Infinity); -} - -function _getLocation () { - return _location || null; + cookies.setItem("location", location, Infinity); } const VoterStore = createStore({ hasDeviceId: function () { - if (_voter_device_id) return true; - else return false; + return _voter_device_id ? true : false; + }, + + hasVoterId: function () { + return _voter_id ? true : false; + }, + + hasLocation: function () { + return _location ? true : false; + }, + + /** + * get the RAW JSON object from api.wevoteusa and merge + * it with other calculated values object + * @param {Function} callback (err, voter-object) + */ + getVoterObject: function (callback) { + _voter = assign({}, { + voter_id: _voter_id, + voter_device_id: _voter_device_id, + location: _location + }); + + if ( _voter.status === "VOTER_FOUND") + return callback(null, assign({}, _voter)); + + return $ajax({ + endpoint: "voterRetrieve", + success: (res) => { + _voter = assign({}, _voter, res); + callback(null, assign({}, _voter)); + }, + error: (err) => { + callback(err); + } + }); + }, + /** * initialize the voter store with data, if no data * and callback with the voter items - * @return {Boolean} + * @param {Function} callback (err, device_id) */ getDeviceId: function (callback) { - if (!callback || typeof callback !== 'function') - throw new Error('VoterStore: getDeviceId must be called with callback'); + if (callback instanceof Function === false) + throw new Error("VoterStore: getDeviceId must be called with callback"); - if (this.hasDeviceId()) callback(null, false, _voter_device_id); + if (_voter_device_id) return callback(null, _voter_device_id); - else $ajax({ + return $ajax({ endpoint: "deviceIdGenerate", success: (res) => { var { voter_device_id: id } = res; - setDeviceId(id); + _setDeviceId(id); callback(null, true, id); }, error: (err) => { callback(err, true, null); } }); + + }, + + /** + * create a voter using voterCreate endpoint from api.wevoteusa + * @param {Function} callback (err, voter_id) + */ + createVoter: function (callback) { + if (callback instanceof Function === false) + throw new Error("VoterStore: getDeviceId must be called with callback"); + + if (_voter_id) return callback(null, _voter_id); + + return $ajax({ + endpoint: "voterCreate", + success: (res) => { + var {voter_id} = res; + _setVoterId(voter_id); + callback(null, voter_id); + }, + error: (err) => callback(err) + }); + }, /** * get the Voters location - * @return {String} location + * @param {Function} callback function (error, location) */ getLocation: function (callback) { - if (_location) callback(null, _location) - else $ajax({ + if (callback instanceof Function === false) + throw new Error("VoterStore: getDeviceId must be called with callback"); + + if (_location) return callback(null, _location); + + return $ajax({ endpoint: "voterAddressRetrieve", success: (res) => { var { text_for_map_search: location } = res; - setLocation(location); + _setLocation(location); callback(null, location); }, error: (err) => callback(err) @@ -84,39 +142,30 @@ const VoterStore = createStore({ }, /** - * save the voter's location to override API IP guess + * save the voter"s location to override API IP guess * @param {String} location * @param {Function} callback function that accepts (err,location) */ saveLocation: function (location, callback) { - if (typeof location !== "string") throw new Error('missing location to save'); - if (callback instanceof Function === false) throw new Error('missing callback function'); + if (typeof location !== "string") throw new Error("missing location to save"); + if (callback instanceof Function === false) throw new Error("missing callback function"); $ajax({ - type: 'POST', + type: "POST", data: { text_for_map_search: location }, - endpoint: 'voterAddressSave', + endpoint: "voterAddressSave", success: (res) => { - var { text_for_map_search: location } = res; + var { text_for_map_search: savedLocation } = res; - setLocation(location); - callback(null, location); + _setLocation(savedLocation); + callback(null, savedLocation); }, error: (err) => callback(err, null) - }) + }); }, signInStatus: function (callback) { - callback(assign({}, _voter)) - }, - - getVoterObject: function () { - //console.log("VoterStore getVoterObject"); - //for (var key in _voter_store) { - // _voter = _voter_store[key]; - //} - //return _voter; - return assign({}, _voter); + callback(assign({}, _voter)); }, /** @@ -129,15 +178,17 @@ const VoterStore = createStore({ }, getVoterPhotoURL: function (callback) { - console.log("VoterStore getVoterPhotoURL"); - if ( _voter_photo_url ) callback(_voter_photo_url); - else callback(new Error('missing voter photo url')); + console.log("VoterStore getVoterPhotoURL"); + if ( _voter_photo_url ) callback(_voter_photo_url); + else callback(new Error("missing voter photo url")); }, /** * set geographical location of voter */ - setGeoLocation() { + setGeoLocation () { + var _position = {}; + navigator.geolocation.getCurrentPosition(function (pos) { _position.lat = pos.coords.latitude; _position.long = pos.coords.longitude; @@ -145,13 +196,13 @@ const VoterStore = createStore({ }, changeLocation: function (location) { - if (!location) throw new Error('Missing voter location'); + if (!location) throw new Error("Missing voter location"); - console.log('setting initial location...'); + console.log("setting initial location..."); console.warn(`we will need to configure logic & etc. for setting up a voters location... `); - cookies.setItem('location', location); + cookies.setItem("location", location); VoterActions.ChangeLocation(location); return location; diff --git a/src/js/utils/object-utils.js b/src/js/utils/object-utils.js index b8c01889a..c6a9d8d7e 100644 --- a/src/js/utils/object-utils.js +++ b/src/js/utils/object-utils.js @@ -7,20 +7,3 @@ export function shallowClone (obj) { } return target; } - -export function cloneWithCandidates (obj) { - let target = {}; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - if (i === 'candidate_list') { - target[i] = []; - obj[i].forEach(c => - target[i].push(shallowClone(_candidate_store[c])) - ) - } else { - target[i] = obj[i]; - } - } - } - return target; -} diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 8f224de2c..25b7a94ab 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -3,37 +3,41 @@ * of many repetitive service calls that we will be using. * @author Nick Fiorini */ +"use strict"; -'use strict'; const DEBUG = false; -const url = require('url'); +const url = require("url"); -const assign = require('object-assign'); -const ajax = require('../vendor/jquery.js').ajax; -const webAppConfig = require('../config'); -const cookies = require('./cookies'); +const assign = require("object-assign"); +const ajax = require("../vendor/jquery.js").ajax; +const webAppConfig = require("../config"); +const cookies = require("./cookies"); + +import * as request from "superagent"; const defaults = { - dataType: 'json', + dataType: "json", baseUrl: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, url: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, query: {}, - type: 'GET', - data: { - voter_device_id: cookies.getItem('voter_device_id') + type: "GET", + data: function () { + return { + voter_device_id: cookies.getItem("voter_device_id") + }; }, - success: (res) => console.warn('Success function not defined:', res), + success: (res) => console.warn("Success function not defined:", res), error: (err) => console.error(err.message) }; export function $ajax (options) { - if (!options.endpoint) throw new Error('$ajax missing endpoint option'); + if (!options.endpoint) throw new Error("$ajax missing endpoint option"); - options.data = assign({}, defaults.data, options.data || {}); + options.data = assign({}, defaults.data(), options.data || {}); options.crossDomain = true; options.success = options.success || defaults.success; options.error = options.error || defaults.error; - options.url = url.resolve(defaults.baseUrl, options.endpoint); + options.url = url.resolve(defaults.baseUrl, options.endpoint) + "/"; return ajax(options); } @@ -43,7 +47,7 @@ export function get (options) { opts.url = url.resolve(opts.baseUrl, opts.endpoint); - return new Promise( (resolve, reject) => new request.Request('GET', opts.url) + return new Promise( (resolve, reject) => new request.Request("GET", opts.url) .accept(opts.dataType) .query(opts.query) .withCredentials() @@ -62,14 +66,14 @@ export function get (options) { resolve(res.body); } }) - ) + ); } export function $post (options) { var opts = assign(defaults, options); - opts.url = url.resolve(opts.baseUrl, opts.endpoint) + '/'; + opts.url = url.resolve(opts.baseUrl, opts.endpoint) + "/"; - return new Promise( (resolve, reject) => new request.Request('POST', opts.url) + return new Promise( (resolve, reject) => new request.Request("POST", opts.url) .accept(opts.dataType) .withCredentials() .send(opts.send) @@ -88,32 +92,32 @@ export function $post (options) { resolve(res.body); } }) - ) + ); } export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { return get({ - endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', + endpoint: "voterBallotItemsRetrieveFromGoogleCivic", query: { text_for_map_search }, success }); } export function candidatesRetrieve (office_we_vote_id, success ) { return get({ - endpoint: 'candidatesRetrieve', + endpoint: "candidatesRetrieve", query: { office_we_vote_id }, success }); } // get the ballot items export function voterBallotItemsRetrieve (success) { - return get({ endpoint: 'voterBallotItemsRetrieve', success }); + return get({ endpoint: "voterBallotItemsRetrieve", success }); } export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, success ) { return get({ - endpoint: 'positionOpposeCountForBallotItem', + endpoint: "positionOpposeCountForBallotItem", query: { id, kind_of_ballot_item }, success }); } @@ -121,93 +125,93 @@ export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, succe // get measure support an opposition export function positionSupportCountForBallotItem (id, kind_of_ballot_item, success ) { return get({ - endpoint: 'positionSupportCountForBallotItem', + endpoint: "positionSupportCountForBallotItem", query: { id, kind_of_ballot_item }, success }); } -export function voterPositionRetrieve (ballot_item_we_vote_id, kind_of_ballot_item, success ) { +export function voterPositionRetrieve (ballot_item_we_vote_id, kind_of_ballot_item, success ) { return get({ - endpoint: 'voterPositionRetrieve', + endpoint: "voterPositionRetrieve", query: { ballot_item_we_vote_id, kind_of_ballot_item }, success }); } export function voterStarStatusRetrieve (id, kind_of_ballot_item, success ) { return get({ - endpoint: 'voterStarStatusRetrieve', + endpoint: "voterStarStatusRetrieve", query: { id, kind_of_ballot_item }, success }); } export function voterStarOnSave (id, kind_of_ballot_item, success ) { return get({ - endpoint: 'voterStarOnSave', + endpoint: "voterStarOnSave", query: { id, kind_of_ballot_item }, success }); } export function voterStarOffSave (id, kind_of_ballot_item, success ) { return get({ - endpoint: 'voterStarOffSave', + endpoint: "voterStarOffSave", query: { id, kind_of_ballot_item }, success }); } export function voterSupportingSave (id, kind_of_ballot_item, success ) { - console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); + console.log("voterSupportingSave, id:, ", id); return get({ - endpoint: 'voterSupportingSave', + endpoint: "voterSupportingSave", query: { id, kind_of_ballot_item }, success }); } export function voterStopSupportingSave (id, kind_of_ballot_item, success ) { - console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopSupportingSave, we_vote_id:, ", id); return get({ - endpoint: 'voterStopSupportingSave', + endpoint: "voterStopSupportingSave", query: { id, kind_of_ballot_item }, success }); } export function voterOpposingSave (id, kind_of_ballot_item, success ) { - console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterOpposingSave, id:, ", id); return get({ - endpoint: 'voterOpposingSave', + endpoint: "voterOpposingSave", query: { id, kind_of_ballot_item }, success }); } export function voterStopOpposingSave (id, kind_of_ballot_item, success ) { - console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopOpposingSave, we_vote_id:, ", id); return get({ - endpoint: 'voterStopOpposingSave', + endpoint: "voterStopOpposingSave", query: { id, kind_of_ballot_item }, success }); } export function deviceIdGenerate (success) { - console.log('generating device id...'); + console.log("generating device id..."); - return get({ endpoint: 'deviceIdGenerate', success }); + return get({ endpoint: "deviceIdGenerate", success }); } export function createVoter (success) { - console.log('creating voter id'); + console.log("creating voter id"); - return get({ endpoint: 'voterCreate', success }); + return get({ endpoint: "voterCreate", success }); } export function voterLocationRetrieveFromIP (success) { - console.log('retrieve location from IP'); + console.log("retrieve location from IP"); - return get({ endpoint: 'voterLocationRetrieveFromIP', success }); + return get({ endpoint: "voterLocationRetrieveFromIP", success }); } export function voterRetrieve (success) { - return get({ endpoint: 'voterRetrieve', success }); + return get({ endpoint: "voterRetrieve", success }); } From 5657d34acafcb7f7790f256bd31e4781fb83dfa8 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Wed, 17 Feb 2016 16:37:25 -0800 Subject: [PATCH 39/92] Load elements of candidate page on load --- src/js/actions/BallotActions.js | 18 +++++ src/js/components/Ballot/PositionList.jsx | 5 ++ src/js/constants/BallotConstants.js | 4 +- src/js/routes/Ballot/Candidate.jsx | 40 ++++++++--- src/js/stores/BallotStore.js | 81 +++++++++++++++++++++-- 5 files changed, 131 insertions(+), 17 deletions(-) diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index 536bd14c4..cc93597c7 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -14,6 +14,24 @@ module.exports = { }); }, + starStatusRetrieved: function (payload, we_vote_id) { + console.log("action payload:"); + console.log(payload); + AppDispatcher.dispatch({ + actionType: BallotConstants.STAR_STATUS_RETRIEVED, + payload: payload, + we_vote_id: we_vote_id + }); + }, + + candidateRetrieved: function (payload) { + AppDispatcher.dispatch({ + actionType: BallotConstants.CANDIDATE_RETRIEVED, + payload: payload, + we_vote_id: payload.we_vote_id + }); + }, + voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE AppDispatcher.dispatch({ actionType: BallotConstants.VOTER_SUPPORTING_SAVE, diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index 3aea58c65..6c3deed2c 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -27,6 +27,11 @@ export default class PositionList extends Component { } render () { + if (!this.state.position_list){ + return ( +
      + ); + } return (
        { this.state.position_list.map( item => diff --git a/src/js/constants/BallotConstants.js b/src/js/constants/BallotConstants.js index 52c3bf062..f7903e4f6 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -6,5 +6,7 @@ module.exports = require('keymirror')({ VOTER_STOP_OPPOSING_SAVE: null, VOTER_STAR_ON_SAVE: null, VOTER_STAR_OFF_SAVE: null, - POSITIONS_RETRIEVED: null + POSITIONS_RETRIEVED: null, + CANDIDATE_RETRIEVED: null, + STAR_STATUS_RETRIEVED: null }); diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 36c3dad64..d7e5228a5 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -12,34 +12,50 @@ import StarAction from '../../components/StarAction'; export default class Candidate extends Component { static propTypes = { //history: PropTypes.func.isRequired, - history: PropTypes.string, - oppose_on: PropTypes.boolean, - params: PropTypes.object.isRequired, - support_on: PropTypes.boolean + // history: PropTypes.string, + // oppose_on: PropTypes.boolean, + params: PropTypes.object.isRequired + // support_on: PropTypes.boolean }; constructor(props) { + console.log(props); super(props); this.state = { candidate: {} }; } componentWillMount(){ // Redirects to root if candidate isn't fetched yet; TODO: just fetch params to enable sending links to candidate page. - var candidate = BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id); - if (Object.keys(candidate).length === 0) - { - this.props.history.replace('/ballot'); - } + // var candidate = BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id); + // if (Object.keys(candidate).length === 0) + // { + // this.props.history.replace('/ballot'); + // } + + } + + componentWillUnmount () { + BallotStore.removeChangeListener(this._onChange.bind(this)); } componentDidMount(){ + BallotStore.addChangeListener(this._onChange.bind(this)); + var candidate = BallotStore.getOrFetchCandidateByWeVoteId(this.props.params.we_vote_id); + if (candidate) { + this.setState({ candidate: candidate }); + } + } + + _onChange(){ this.setState({ candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }); + console.log('on_Change!'); + console.log(this.state.candidate); } render() { var candidate = this.state.candidate; var we_vote_id = this.props.params.we_vote_id; - if (!candidate.we_vote_id){ + if (!candidate || !candidate.we_vote_id){ return (
        ); }; // var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); @@ -128,7 +144,9 @@ export default class Candidate extends Component {
      */} - + { + + }
      diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index baa0a56e6..fe7559541 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -1,4 +1,5 @@ import { get } from '../utils/service'; +// import service from '../utils/service'; import { createStore } from '../utils/createStore'; import { shallowClone } from '../utils/object-utils'; @@ -41,6 +42,14 @@ const BallotAPIWorker = { success: success || defaultSuccess }); }, + candidateRetrieve: function (we_vote_id, success ) { + return get({ + endpoint: 'candidateRetrieve', + query: { candidate_we_vote_id: we_vote_id }, + success: success + }); + }, + // get the ballot items voterBallotItemsRetrieve: function ( success ) { return get({ endpoint: 'voterBallotItemsRetrieve', @@ -48,7 +57,7 @@ const BallotAPIWorker = { }, positionListForBallotItem : function( we_vote_id, success) { - return service.get({ + return get({ endpoint: 'positionListForBallotItem', query: { ballot_item_id: _ballot_store[we_vote_id].id, @@ -93,8 +102,8 @@ const BallotAPIWorker = { return get({ endpoint: 'voterStarStatusRetrieve', query: { - ballot_item_id: _ballot_store[we_vote_id].id, - kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item + ballot_item_we_vote_id: we_vote_id, + kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item || 'CANDIDATE' }, success: success || defaultSuccess }); }, @@ -408,6 +417,38 @@ const BallotStore = createStore({ return _ballot_store[we_vote_id].is_starred; }, + getOrFetchCandidateByWeVoteId: function (candidate_we_vote_id) { + var candidate = this.getCandidateByWeVoteId(candidate_we_vote_id); + if (candidate && candidate.is_oppose) { return candidate; } //candidate already retrieved + _ballot_store[candidate_we_vote_id] = {}; + _ballot_store[candidate_we_vote_id].kind_of_ballot_item = 'CANDIDATE'; + + BallotAPIWorker.candidateRetrieve(candidate_we_vote_id, function(res){ + BallotActions.candidateRetrieved(res); + BallotStore.fetchCandidateDetails(candidate_we_vote_id); + }); + + return _ballot_store[candidate_we_vote_id]; + }, + + fetchCandidateDetails: function(candidate_we_vote_id){ + BallotAPIWorker.voterStarStatusRetrieve(candidate_we_vote_id, function(res){ + BallotActions.starStatusRetrieved(res, candidate_we_vote_id); + }); + + BallotStore.fetchCandidatePositionsByWeVoteId(candidate_we_vote_id); + + BallotAPIWorker.positionOpposeCountForBallotItem (candidate_we_vote_id, function(res){ + _ballot_store[candidate_we_vote_id].opposeCount = res.count; + BallotStore.emitChange(); + }); + + BallotAPIWorker.positionSupportCountForBallotItem (candidate_we_vote_id, function(res){ + _ballot_store [candidate_we_vote_id].supportCount = res.count; + BallotStore.emitChange(); + }); + }, + getCandidateByWeVoteId: function (candidate_we_vote_id) { return shallowClone ( _ballot_store [ candidate_we_vote_id ] @@ -453,6 +494,11 @@ function setLocalPositionsList(we_vote_id, position_list) { return true; } +function addCandidateToStore (res) { + _ballot_store[res.we_vote_id] = res; + return true; +} + /** * toggle the star state of a ballot item by its we_vote_id * @param {string} we_vote_id identifier for lookup in stored @@ -465,6 +511,13 @@ function toggleStarState(we_vote_id) { return true; } +function setStarState(we_vote_id, status) { + if (status){ + _ballot_store[we_vote_id].is_starred = status; + } + return true; +} + /** * toggle the support state of a ballot item to on by its we_vote_id */ @@ -525,10 +578,28 @@ function setLocalOpposeOffState(we_vote_id) { return true; } -AppDispatcher.register( action => { - var { we_vote_id } = action; + +BallotStore.dispatchToken = AppDispatcher.register( action => { + var { we_vote_id } = action; switch (action.actionType) { + case BallotConstants.CANDIDATE_RETRIEVED: + console.log('action:'); + console.log(action); + addCandidateToStore(action.payload) + && BallotStore.emitChange(); + break; + + case BallotConstants.STAR_STATUS_RETRIEVED: + // AppDispatcher.waitFor([BallotStore.dispatchToken]); + console.log("REGISTERING"); + console.log(action); + console.log("Ballot Store:"); + console.log(_ballot_store[action.we_vote_id]); + setStarState(action.we_vote_id, action.payload.is_starred) + && BallotStore.emitChange(); + break; + case BallotConstants.POSITIONS_RETRIEVED: setLocalPositionsList(action.we_vote_id, action.payload.position_list) && BallotStore.emitChange(); From 834a6f7c55bd835a3a1f24125959e43b4df4188d Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Wed, 17 Feb 2016 18:39:48 -0800 Subject: [PATCH 40/92] Save changes --- src/js/Application.jsx | 1 + src/js/actions/BallotActions.js | 9 ++++++ src/js/components/Ballot/PositionItem.jsx | 2 +- src/js/components/Ballot/PositionList.jsx | 6 ++-- src/js/constants/BallotConstants.js | 3 +- src/js/routes/Ballot/Ballot.jsx | 2 +- src/js/routes/Ballot/Candidate.jsx | 3 -- src/js/stores/BallotStore.js | 37 ++++++++++++++++++----- src/js/utils/service.js | 1 + 9 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index a9ab877e0..4bf9f94ee 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -22,6 +22,7 @@ export default class Application extends Component { console.log("Application: About to initialize VoterStore"); VoterStore.signInStatus((voter) => { console.log(voter, 'voter is your object') + console.log(voter) this.setState({voter}); }); } diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index cc93597c7..29f3acd99 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -24,6 +24,15 @@ module.exports = { }); }, + candidateItemRetrieved: function (payload, parameter, we_vote_id){ + AppDispatcher.dispatch({ + actionType: BallotConstants.CANDIDATE_DETAIL_RETRIEVED, + payload: payload, + parameter: parameter, + we_vote_id: we_vote_id + }); + }, + candidateRetrieved: function (payload) { AppDispatcher.dispatch({ actionType: BallotConstants.CANDIDATE_RETRIEVED, diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 536b1e662..98ac1185f 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -24,7 +24,7 @@ export default class PositionItem extends Component { _onChange () { this.setState({ position: PositionStore.getLocalPositionByWeVoteId(this.props.position_we_vote_id) }); - console.log("Position:") + console.log("Position:"); console.log(this.state.position); } diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index 6c3deed2c..c9d95ede1 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -11,7 +11,7 @@ export default class PositionList extends Component { super(props); this.state = { position_list: [] }; } - // no candidate exists... go to ballot + componentDidMount (){ BallotStore.fetchCandidatePositionsByWeVoteId(this.props.we_vote_id) BallotStore.addChangeListener(this._onChange.bind(this)); @@ -35,7 +35,9 @@ export default class PositionList extends Component { return (
        { this.state.position_list.map( item => - ) + ) }
      ); diff --git a/src/js/constants/BallotConstants.js b/src/js/constants/BallotConstants.js index f7903e4f6..0d6cb307b 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -8,5 +8,6 @@ module.exports = require('keymirror')({ VOTER_STAR_OFF_SAVE: null, POSITIONS_RETRIEVED: null, CANDIDATE_RETRIEVED: null, - STAR_STATUS_RETRIEVED: null + STAR_STATUS_RETRIEVED: null, + CANDIDATE_DETAIL_RETRIEVED: null }); diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 34ed28403..eaa7f392e 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -16,7 +16,7 @@ export default class Ballot extends Component { } componentDidMount () { - // BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) + BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) } render () { diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index d7e5228a5..958095874 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -19,7 +19,6 @@ export default class Candidate extends Component { }; constructor(props) { - console.log(props); super(props); this.state = { candidate: {} }; } @@ -58,8 +57,6 @@ export default class Candidate extends Component { if (!candidate || !candidate.we_vote_id){ return (
      ); }; - // var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); - var support_item; if (this.props.support_on) { diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index fe7559541..f5f0d3919 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -1,5 +1,4 @@ import { get } from '../utils/service'; -// import service from '../utils/service'; import { createStore } from '../utils/createStore'; import { shallowClone } from '../utils/object-utils'; @@ -50,6 +49,14 @@ const BallotAPIWorker = { }); }, + officeRetrieve: function (we_vote_id, success ) { + return get({ + endpoint: 'officeRetrieve', + query: { office_we_vote_id: we_vote_id }, + success: success + }); + }, + // get the ballot items voterBallotItemsRetrieve: function ( success ) { return get({ endpoint: 'voterBallotItemsRetrieve', @@ -372,6 +379,16 @@ const BallotStore = createStore({ }); }, + fetchCandidateOfficeByWeVoteId: function(candidate_we_vote_id){ + var we_vote_id = _ballot_store[candidate_we_vote_id].contest_office_we_vote_id; + BallotAPIWorker.officeRetrieve(we_vote_id, + function(res){ + console.log("fetchCandidateResponse"); + console.log(res); + BallotActions.candidateItemRetrieved(candidate_we_vote_id, 'office_display_name', res); + }); + }, + /** * get the number of orgs and friends that the Voter follows who support this ballot item * @param {String} we_vote_id ballot items we_vote_id @@ -437,6 +454,7 @@ const BallotStore = createStore({ }); BallotStore.fetchCandidatePositionsByWeVoteId(candidate_we_vote_id); + BallotStore.fetchCandidateOfficeByWeVoteId(candidate_we_vote_id); BallotAPIWorker.positionOpposeCountForBallotItem (candidate_we_vote_id, function(res){ _ballot_store[candidate_we_vote_id].opposeCount = res.count; @@ -494,6 +512,11 @@ function setLocalPositionsList(we_vote_id, position_list) { return true; } +function setCandidateDetail(we_vote_id, parameter, value) { + _ballot_store[we_vote_id][parameter] = value; + return true; +} + function addCandidateToStore (res) { _ballot_store[res.we_vote_id] = res; return true; @@ -583,19 +606,17 @@ BallotStore.dispatchToken = AppDispatcher.register( action => { var { we_vote_id } = action; switch (action.actionType) { + case BallotConstants.CANDIDATE_DETAIL_RETRIEVED: + setCandidateDetail(action.payload.we_vote_id, action.payload.parameter, action.payload) && + BallotStore.emitChange(); + break; + case BallotConstants.CANDIDATE_RETRIEVED: - console.log('action:'); - console.log(action); addCandidateToStore(action.payload) && BallotStore.emitChange(); break; case BallotConstants.STAR_STATUS_RETRIEVED: - // AppDispatcher.waitFor([BallotStore.dispatchToken]); - console.log("REGISTERING"); - console.log(action); - console.log("Ballot Store:"); - console.log(_ballot_store[action.we_vote_id]); setStarState(action.we_vote_id, action.payload.is_starred) && BallotStore.emitChange(); break; diff --git a/src/js/utils/service.js b/src/js/utils/service.js index cdf89ad22..49d708bae 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -12,6 +12,7 @@ const assign = require('object-assign'); const ajax = require('../vendor/jquery.js').ajax; const webAppConfig = require('../config'); const cookies = require('./cookies'); +import * as request from "superagent"; const defaults = { dataType: 'json', From ea15baf7f4e8823ffc88fab6465fc16b498ab5c6 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Wed, 17 Feb 2016 19:50:52 -0800 Subject: [PATCH 41/92] Updated "get" in service.js to always include the voter_device_id with all endpoint calls. Added GuidePositionList back to Root.jsx --- src/js/Root.jsx | 4 ++++ src/js/utils/service.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/js/Root.jsx b/src/js/Root.jsx index 603d37d68..6e319cbd8 100644 --- a/src/js/Root.jsx +++ b/src/js/Root.jsx @@ -22,6 +22,7 @@ import Candidate from "./routes/Ballot/Candidate"; /* Ballot Off-shoot Pages */ import Opinions from "./routes/Opinions"; +import GuidePositionList from './routes/Guide/PositionList'; // A list of all positions from one guide /* More */ import More from "./routes/More"; @@ -73,6 +74,9 @@ const routes = (firstVisit, voter) => + {/* Voter Guide Pages */} + + { firstVisit ? : } diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 25b7a94ab..8715aaabc 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -46,6 +46,8 @@ export function get (options) { var opts = assign(defaults, options); opts.url = url.resolve(opts.baseUrl, opts.endpoint); + // We add voter_device_id to all endpoint calls + opts.query["voter_device_id"] = cookies.getItem("voter_device_id"); return new Promise( (resolve, reject) => new request.Request("GET", opts.url) .accept(opts.dataType) @@ -112,7 +114,9 @@ export function candidatesRetrieve (office_we_vote_id, success ) { // get the ballot items export function voterBallotItemsRetrieve (success) { - return get({ endpoint: "voterBallotItemsRetrieve", success }); + return get({ + endpoint: "voterBallotItemsRetrieve", + success }); } export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, success ) { From f750168bbf2c82de42f7552aa6cb97bde542a03d Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Wed, 17 Feb 2016 22:51:18 -0800 Subject: [PATCH 42/92] Got the menu working again. Passing voter_device_id as a GET variable in the VoterGuideStore, so the "More Opinions" page works again. --- src/js/Application.jsx | 2 +- src/js/routes/More/About.jsx | 1 + src/js/stores/BallotStore.js | 2 +- src/js/stores/VoterGuideStore.js | 19 +++++++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index 12fbd90f5..12c52facf 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -38,7 +38,7 @@ export default class Application extends Component {
      - { is_signed_in ? : } + { is_signed_in ? : }
      { this.props.children } diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index 4f78621a7..eabcc094b 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -66,6 +66,7 @@ export default class About extends Component { Kim Anderson - San Francisco, CA
      Adam Barry - San Francisco, CA
      Raphael Merx - San Francisco, CA
      + Jesse Aldridge - San Francisco, CA
      Josh Levinger - Oakland, CA
      Leslie Castellanos - San Francisco, CA
      Jennifer Holmes - Pacifica, CA
      diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 7037692c7..4bf28788f 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -13,7 +13,7 @@ let _google_civic_election_id = null; const MEASURE = 'MEASURE'; function defaultSuccess (res) { - console.warn(res); + // console.warn(res); } function addItemsToBallotStore (ballot_item_list) { diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 897eaf206..932282c86 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -1,10 +1,12 @@ import BallotStore from '../stores/BallotStore'; import { createStore } from '../utils/createStore'; +import { get } from '../utils/service'; import {shallowClone} from '../utils/object-utils'; const AppDispatcher = require('../dispatcher/AppDispatcher'); const VoterGuideConstants = require('../constants/VoterGuideConstants'); +const cookies = require("../utils/cookies"); const request = require('superagent'); const web_app_config = require('../config'); @@ -32,6 +34,7 @@ function retrieveVoterGuidesToFollowList () { .withCredentials() .query(web_app_config.test) .query({ google_civic_election_id: BallotStore.getGoogleCivicElectionId() }) + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .end( function (err, res) { if (err || !res.body.success) reject(err || res.body.status); @@ -59,6 +62,7 @@ function retrieveVoterGuidesFollowedList () { return new Promise( (resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}voterGuidesFollowedRetrieve/`) .withCredentials() + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .end( function (err, res) { if (err || !res.body.success) reject(err || res.body.status); @@ -88,6 +92,7 @@ function retrieveOrganizations (data) { .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationRetrieve/`) .withCredentials() .query({ organization_we_vote_id: we_vote_id }) + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .end( function (err, res) { if (res.body.success) { _organization_store[res.body.organization_we_vote_id] = shallowClone(res.body); @@ -109,6 +114,7 @@ function retrieveOrganizationsFollowedList () { return new Promise( (resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationsFollowedRetrieve/`) .withCredentials() + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .end( function (err, res) { if (err || !res.body.success) reject(err || res.body.status); @@ -132,6 +138,7 @@ function followOrganization (we_vote_id) { return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationFollow/`) .withCredentials() + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .query({ organization_id: _organization_store[we_vote_id].organization_id }) .end( function (err, res) { if (res.body.success) { @@ -151,6 +158,7 @@ function ignoreOrganization (we_vote_id) { return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationFollowIgnore/`) .withCredentials() + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .query({ organization_id: _organization_store[we_vote_id].organization_id }) .end( function (err, res) { if (res.body.success) { @@ -170,6 +178,7 @@ function stopFollowingOrganization (we_vote_id) { return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationStopFollowing/`) .withCredentials() + .query({ voter_device_id: cookies.getItem("voter_device_id") }) .query({ organization_id: _organization_store[we_vote_id].id }) .end( function (err, res) { if (res.body.success) { @@ -183,6 +192,16 @@ function stopFollowingOrganization (we_vote_id) { ); } +// Refactor to this structure? +//const VoterGuideAPIWorker = { +// voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success) { +// return get({ +// endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', +// query: {text_for_map_search}, success: success || defaultSuccess +// }); +// } +//}; + const VoterGuideStore = createStore({ /** * initialize the voter guide store with "guides to follow" data, if no data From fa69a0b60575b01e40674791e3a6852a5ec11d8e Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Thu, 18 Feb 2016 05:56:26 -0800 Subject: [PATCH 43/92] Updated call to voterAddressSave endpoint to GET (from POST) to reflect change on API Server. --- src/js/stores/VoterStore.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 1e250c350..77bf7ee35 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -67,7 +67,6 @@ const VoterStore = createStore({ callback(err); } }); - }, /** @@ -151,7 +150,7 @@ const VoterStore = createStore({ if (callback instanceof Function === false) throw new Error("missing callback function"); $ajax({ - type: "POST", + type: "GET", data: { text_for_map_search: location }, endpoint: "voterAddressSave", success: (res) => { From 23f3a54f263caa5a28235e99d1118b3ec814d1a5 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Thu, 18 Feb 2016 11:33:17 -0500 Subject: [PATCH 44/92] save location update --- src/js/routes/Settings/Location.jsx | 78 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/src/js/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index 808e35964..6c074184a 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -1,18 +1,18 @@ -import React, { Component } from 'react'; -import { Button, ButtonToolbar } from 'react-bootstrap'; -import HeaderBackNavigation from '../../components/Navigation/HeaderBackNavigation'; -import VoterStore from '../../stores/VoterStore'; +import React, { Component } from "react"; +import { Button, ButtonToolbar } from "react-bootstrap"; +import HeaderBackNavigation from "../../components/Navigation/HeaderBackNavigation"; +import VoterStore from "../../stores/VoterStore"; export default class Location extends Component { - constructor(props) { + constructor (props) { super(props); - this.state = {} + this.state = {}; } - componentDidMount() { - VoterStore.getLocation( location => { + componentDidMount () { + VoterStore.getLocation( (err, location) => { this.setState({ location }); - }) + }); } updateLocation (e) { @@ -21,49 +21,47 @@ export default class Location extends Component { }); } - saveLocation (e) { + saveLocation () { var { location } = this.state; - VoterStore.saveLocation ( location, (err, location) => { + VoterStore.saveLocation( location, (err) => { if (err) return console.error(err); - window.location.href="/ballot" + window.location.href = "/ballot"; }); } - render() { + render () { var { location } = this.state; - return ( -
      -
      -

      - Change Location -

      -
      - - Please enter the address (or just the city) where you registered to - vote. The more location information you can provide, the more ballot information will - be visible. - + return
      +
      +

      + Change Location +

      +
      + + Please enter the address (or just the city) where you registered to + vote. The more location information you can provide, the more ballot information will + be visible. + - + - - + + - -
      -
      + +
      - ); +
      ; } } From 30a4ebadd788690ad2b4d40976e845824a318c4b Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Thu, 18 Feb 2016 11:47:04 -0500 Subject: [PATCH 45/92] fixing build fail in service.js file --- src/js/utils/service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 8715aaabc..aca78ee5b 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -47,7 +47,7 @@ export function get (options) { opts.url = url.resolve(opts.baseUrl, opts.endpoint); // We add voter_device_id to all endpoint calls - opts.query["voter_device_id"] = cookies.getItem("voter_device_id"); + opts.query.voter_device_id = cookies.getItem("voter_device_id"); return new Promise( (resolve, reject) => new request.Request("GET", opts.url) .accept(opts.dataType) From 4aea4f5cb2d7201df47ab4f6bbd86c76198266ee Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Thu, 18 Feb 2016 11:16:37 -0800 Subject: [PATCH 46/92] Remove change to service.js --- src/js/utils/service.js | 250 ++++++++++++++++++++-------------------- 1 file changed, 124 insertions(+), 126 deletions(-) diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 924a840b1..8715aaabc 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -46,7 +46,6 @@ export function get (options) { var opts = assign(defaults, options); opts.url = url.resolve(opts.baseUrl, opts.endpoint); - var thisUrl = opts.url; // We add voter_device_id to all endpoint calls opts.query["voter_device_id"] = cookies.getItem("voter_device_id"); @@ -61,11 +60,10 @@ export function get (options) { reject(err); } else { - if (opts.success instanceof Function === true){ - opts.success(res.body);} - else if (DEBUG){ + if (opts.success instanceof Function === true) + opts.success(res.body); + else if (DEBUG) console.warn(res.body); - } resolve(res.body); } @@ -100,124 +98,124 @@ export function $post (options) { } -// export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { -// return get({ -// endpoint: "voterBallotItemsRetrieveFromGoogleCivic", -// query: { text_for_map_search }, success -// }); -// } -// -// export function candidatesRetrieve (office_we_vote_id, success ) { -// return get({ -// endpoint: "candidatesRetrieve", -// query: { office_we_vote_id }, success -// }); -// } -// -// // get the ballot items -// export function voterBallotItemsRetrieve (success) { -// return get({ -// endpoint: "voterBallotItemsRetrieve", -// success }); -// } -// -// export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "positionOpposeCountForBallotItem", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// // get measure support an opposition -// export function positionSupportCountForBallotItem (id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "positionSupportCountForBallotItem", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterPositionRetrieve (ballot_item_we_vote_id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "voterPositionRetrieve", -// query: { ballot_item_we_vote_id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterStarStatusRetrieve (id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "voterStarStatusRetrieve", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterStarOnSave (id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "voterStarOnSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterStarOffSave (id, kind_of_ballot_item, success ) { -// return get({ -// endpoint: "voterStarOffSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterSupportingSave (id, kind_of_ballot_item, success ) { -// console.log("voterSupportingSave, id:, ", id); -// -// return get({ -// endpoint: "voterSupportingSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterStopSupportingSave (id, kind_of_ballot_item, success ) { -// console.log("voterStopSupportingSave, we_vote_id:, ", id); -// -// return get({ -// endpoint: "voterStopSupportingSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterOpposingSave (id, kind_of_ballot_item, success ) { -// console.log("voterOpposingSave, id:, ", id); -// -// return get({ -// endpoint: "voterOpposingSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function voterStopOpposingSave (id, kind_of_ballot_item, success ) { -// console.log("voterStopOpposingSave, we_vote_id:, ", id); -// -// return get({ -// endpoint: "voterStopOpposingSave", -// query: { id, kind_of_ballot_item }, success -// }); -// } -// -// export function deviceIdGenerate (success) { -// console.log("generating device id..."); -// -// return get({ endpoint: "deviceIdGenerate", success }); -// } -// -// export function createVoter (success) { -// console.log("creating voter id"); -// -// return get({ endpoint: "voterCreate", success }); -// } -// -// export function voterLocationRetrieveFromIP (success) { -// console.log("retrieve location from IP"); -// -// return get({ endpoint: "voterLocationRetrieveFromIP", success }); -// } -// -// export function voterRetrieve (success) { -// return get({ endpoint: "voterRetrieve", success }); -// } +export function voterBallotItemsRetrieveFromGoogleCivic (text_for_map_search, success ) { + return get({ + endpoint: "voterBallotItemsRetrieveFromGoogleCivic", + query: { text_for_map_search }, success + }); +} + +export function candidatesRetrieve (office_we_vote_id, success ) { + return get({ + endpoint: "candidatesRetrieve", + query: { office_we_vote_id }, success + }); +} + +// get the ballot items +export function voterBallotItemsRetrieve (success) { + return get({ + endpoint: "voterBallotItemsRetrieve", + success }); +} + +export function positionOpposeCountForBallotItem (id, kind_of_ballot_item, success ) { + return get({ + endpoint: "positionOpposeCountForBallotItem", + query: { id, kind_of_ballot_item }, success + }); +} + +// get measure support an opposition +export function positionSupportCountForBallotItem (id, kind_of_ballot_item, success ) { + return get({ + endpoint: "positionSupportCountForBallotItem", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterPositionRetrieve (ballot_item_we_vote_id, kind_of_ballot_item, success ) { + return get({ + endpoint: "voterPositionRetrieve", + query: { ballot_item_we_vote_id, kind_of_ballot_item }, success + }); +} + +export function voterStarStatusRetrieve (id, kind_of_ballot_item, success ) { + return get({ + endpoint: "voterStarStatusRetrieve", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStarOnSave (id, kind_of_ballot_item, success ) { + return get({ + endpoint: "voterStarOnSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStarOffSave (id, kind_of_ballot_item, success ) { + return get({ + endpoint: "voterStarOffSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterSupportingSave (id, kind_of_ballot_item, success ) { + console.log("voterSupportingSave, id:, ", id); + + return get({ + endpoint: "voterSupportingSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStopSupportingSave (id, kind_of_ballot_item, success ) { + console.log("voterStopSupportingSave, we_vote_id:, ", id); + + return get({ + endpoint: "voterStopSupportingSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterOpposingSave (id, kind_of_ballot_item, success ) { + console.log("voterOpposingSave, id:, ", id); + + return get({ + endpoint: "voterOpposingSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function voterStopOpposingSave (id, kind_of_ballot_item, success ) { + console.log("voterStopOpposingSave, we_vote_id:, ", id); + + return get({ + endpoint: "voterStopOpposingSave", + query: { id, kind_of_ballot_item }, success + }); +} + +export function deviceIdGenerate (success) { + console.log("generating device id..."); + + return get({ endpoint: "deviceIdGenerate", success }); +} + +export function createVoter (success) { + console.log("creating voter id"); + + return get({ endpoint: "voterCreate", success }); +} + +export function voterLocationRetrieveFromIP (success) { + console.log("retrieve location from IP"); + + return get({ endpoint: "voterLocationRetrieveFromIP", success }); +} + +export function voterRetrieve (success) { + return get({ endpoint: "voterRetrieve", success }); +} From 1126ddc2a3c07cdb8be416d91b550aaf5434c771 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Thu, 18 Feb 2016 15:15:19 -0800 Subject: [PATCH 47/92] Formatting clean up for 'npm test' eslint src --- src/js/actions/BallotActions.js | 6 +-- src/js/actions/FacebookActionCreators.js | 48 +++++++++--------- src/js/actions/VoterActions.js | 6 +-- src/js/actions/VoterGuideActions.js | 6 +-- src/js/components/Facebook/Main.js | 31 ++++++------ src/js/config.js | 18 +++---- src/js/constants/BallotConstants.js | 2 +- src/js/constants/CandidateConstants.js | 2 +- src/js/constants/FacebookConstants.js | 6 +-- src/js/constants/VoterConstants.js | 2 +- src/js/constants/VoterGuideConstants.js | 2 +- src/js/dispatcher/AppDispatcher.js | 2 +- src/js/dispatcher/FacebookDispatcher.js | 4 +- src/js/index.js | 4 +- src/js/stores/VoterGuideStore.js | 63 ++++++++++++------------ src/js/utils/createStore.js | 14 +++--- src/js/utils/promise-utils.js | 6 +-- 17 files changed, 109 insertions(+), 113 deletions(-) diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index d93b6a450..75489858d 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -1,6 +1,6 @@ -'use strict'; -var AppDispatcher = require('../dispatcher/AppDispatcher'); -var BallotConstants = require('../constants/BallotConstants'); +"use strict"; +var AppDispatcher = require("../dispatcher/AppDispatcher"); +var BallotConstants = require("../constants/BallotConstants"); // In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ VOTER_SUPPORTING_SAVE) // When action calls one of these functions, we are telling the code in the AppDispatcher block to run diff --git a/src/js/actions/FacebookActionCreators.js b/src/js/actions/FacebookActionCreators.js index 2365ba0e0..e17f132a1 100644 --- a/src/js/actions/FacebookActionCreators.js +++ b/src/js/actions/FacebookActionCreators.js @@ -1,48 +1,47 @@ -const web_app_config = require('../config'); -import FacebookDispatcher from '../dispatcher/FacebookDispatcher'; -import FacebookConstants from '../constants/FacebookConstants' +const web_app_config = require("../config"); +import FacebookDispatcher from "../dispatcher/FacebookDispatcher"; +import FacebookConstants from "../constants/FacebookConstants"; const FacebookActionCreators = { - initFacebook: function() { - window.fbAsyncInit = function() { + initFacebook: function () { + window.fbAsyncInit = function () { FB.init({ - appId : web_app_config.FACEBOOK_APP_ID, - xfbml : true, - version : 'v2.5' + appId: web_app_config.FACEBOOK_APP_ID, + xfbml: true, + version: "v2.5" }); // after initialization, get the login status FacebookActionCreators.getLoginStatus(); }, - - (function(d, s, id){ + (function (d, s, id){ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/en_US/sdk.js"; fjs.parentNode.insertBefore(js, fjs); - }(document, 'script', 'facebook-jssdk')); + }(document, "script", "facebook-jssdk")); }, - getLoginStatus: function() { + getLoginStatus: function () { window.FB.getLoginStatus((response) => { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_INITIALIZED, data: response - }) + }); }); }, login: () => { try { window.FB.login((response) => { - if (response.status === 'connected') { + if (response.status === "connected") { // Logged into We Vote and Facebook FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_LOGGED_IN, data: response - }) - } else if (response.status === 'not_authorized') { + }); + } else if (response.status === "not_authorized") { // The person is logged into Facebook, but not We Vote } else { // The person is not logged into Facebook @@ -58,8 +57,8 @@ const FacebookActionCreators = { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_LOGGED_OUT, data: response - }) - }) + }); + }); }, disconnectFromFacebook: () => { @@ -67,22 +66,21 @@ const FacebookActionCreators = { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_SIGN_IN_DISCONNECT, data: true - }) + }); }, getFacebookProfilePicture: (userId) => { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_GETTING_PICTURE, data: null - }) - + }); window.FB.api(`/${userId}/picture?type=large`, (response) => { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_RECEIVED_PICTURE, data: response - }) - }) + }); + }); } -} +}; -module.exports = FacebookActionCreators; \ No newline at end of file +module.exports = FacebookActionCreators; diff --git a/src/js/actions/VoterActions.js b/src/js/actions/VoterActions.js index 414e4b031..503131638 100644 --- a/src/js/actions/VoterActions.js +++ b/src/js/actions/VoterActions.js @@ -1,6 +1,6 @@ -'use strict'; -var AppDispatcher = require('../dispatcher/AppDispatcher'); -var VoterConstants = require('../constants/VoterConstants'); +"use strict"; +var AppDispatcher = require("../dispatcher/AppDispatcher"); +var VoterConstants = require("../constants/VoterConstants"); module.exports = { ChangeLocation: function (location) { // VOTER_LOCATION_RETRIEVE diff --git a/src/js/actions/VoterGuideActions.js b/src/js/actions/VoterGuideActions.js index 26bad55b1..e38b3b1d3 100644 --- a/src/js/actions/VoterGuideActions.js +++ b/src/js/actions/VoterGuideActions.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -import AppDispatcher from '../dispatcher/AppDispatcher'; -import VoterGuideConstants from '../constants/VoterGuideConstants'; +import AppDispatcher from "../dispatcher/AppDispatcher"; +import VoterGuideConstants from "../constants/VoterGuideConstants"; // In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ BALLOT_SUPPORT_ON) // When action calls one of these functions, we are telling the code in the AppDispatcher block to run diff --git a/src/js/components/Facebook/Main.js b/src/js/components/Facebook/Main.js index 89e0383df..6ae42c55a 100644 --- a/src/js/components/Facebook/Main.js +++ b/src/js/components/Facebook/Main.js @@ -1,52 +1,51 @@ -import React from 'react'; +import React from "react"; -import FacebookActionCreators from '../../actions/FacebookActionCreators'; -import FacebookStore from '../../stores/FacebookStore'; -import FacebookLogin from '../../components/Facebook/FacebookLogin'; -import FacebookLogout from '../../components/Facebook/FacebookLogout'; -import FacebookDownloadPicture from '../../components/Facebook/FacebookDownloadPicture'; -import FacebookPicture from '../../components/Facebook/FacebookPicture'; +import FacebookActionCreators from "../../actions/FacebookActionCreators"; +import FacebookStore from "../../stores/FacebookStore"; +import FacebookLogin from "../../components/Facebook/FacebookLogin"; +import FacebookLogout from "../../components/Facebook/FacebookLogout"; +import FacebookDownloadPicture from "../../components/Facebook/FacebookDownloadPicture"; +import FacebookPicture from "../../components/Facebook/FacebookPicture"; class Main extends React.Component { - constructor(props) { + constructor (props) { super(); this.state = this.getFacebookState(); } - getFacebookState() { + getFacebookState () { return { accessToken: FacebookStore.accessToken, loggedIn: FacebookStore.loggedIn, userId: FacebookStore.userId, facebookPictureStatus: FacebookStore.facebookPictureStatus, facebookPictureUrl: FacebookStore.facebookPictureUrl - } + }; } - componentDidMount() { + componentDidMount () { FacebookActionCreators.initFacebook(); FacebookStore.addChangeListener(() => this._onFacebookChange()); } - componentWillUnmount() { + componentWillUnmount () { FacebookStore.removeChangeListener(this._onFacebookChange); } - _onFacebookChange() { + _onFacebookChange () { this.setState(this.getFacebookState()); } - render() { + render () { return (
      {!this.state.loggedIn ? : null} {this.state.loggedIn ? : null} -

      Facebook logged in: {this.state.loggedIn ? 'true' : 'false'}

      +

      Facebook logged in: {this.state.loggedIn ? "true" : "false"}

      Facebook access token: {this.state.accessToken}

      User ID is: {this.state.userId}

      {this.state.userId ? : null} - diff --git a/src/js/config.js b/src/js/config.js index 265b5b82c..9419046ac 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -1,9 +1,9 @@ -// Note that we import these values into 'web_app_config' (so we can search for it) +// Note that we import these values into "web_app_config" (so we can search for it) module.exports = { - WE_VOTE_SERVER_ADMIN_ROOT_URL: 'https://api.wevoteusa.org/admin/', - WE_VOTE_SERVER_API_ROOT_URL: 'https://api.wevoteusa.org/apis/v1/', - // WE_VOTE_SERVER_ADMIN_ROOT_URL: 'http://localhost:8000/admin/', - // WE_VOTE_SERVER_API_ROOT_URL: 'http://localhost:8000/apis/v1/', + WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", + WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", + // WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", + // WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", DEBUG_MODE: true, // Use 1 or 0 as opposed to true or false @@ -12,10 +12,10 @@ module.exports = { }, location: { - text_for_map_search: '2208 Ebb Tide Rd, Virginia Beach, VA 23451' + text_for_map_search: "2208 Ebb Tide Rd, Virginia Beach, VA 23451" }, - // FACEBOOK_APP_ID: '1097389196952441' // DaleMcGrew Facebook App Id, https://wevote.me - FACEBOOK_APP_ID: '1104012436290117' // We Vote - Test App - // FACEBOOK_APP_ID: '868492333200013' // wevote-dev, http://localhost:3000 + // FACEBOOK_APP_ID: "1097389196952441" // DaleMcGrew Facebook App Id, https://wevote.me + FACEBOOK_APP_ID: "1104012436290117" // We Vote - Test App + // FACEBOOK_APP_ID: "868492333200013" // wevote-dev, http://localhost:3000 }; diff --git a/src/js/constants/BallotConstants.js b/src/js/constants/BallotConstants.js index 95bbe1707..5e58e57cf 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -1,5 +1,5 @@ // Ballot Constants -module.exports = require('keymirror')({ +module.exports = require("keymirror")({ VOTER_SUPPORTING_SAVE: null, VOTER_STOP_SUPPORTING_SAVE: null, VOTER_OPPOSING_SAVE: null, diff --git a/src/js/constants/CandidateConstants.js b/src/js/constants/CandidateConstants.js index d4b21028a..083107987 100644 --- a/src/js/constants/CandidateConstants.js +++ b/src/js/constants/CandidateConstants.js @@ -1,4 +1,4 @@ -const keyMirror = require('keymirror'); +const keyMirror = require("keymirror"); module.exports = keyMirror({ CANDIDATE_OPPOSED: null, diff --git a/src/js/constants/FacebookConstants.js b/src/js/constants/FacebookConstants.js index 83f95e88d..09db88389 100644 --- a/src/js/constants/FacebookConstants.js +++ b/src/js/constants/FacebookConstants.js @@ -1,4 +1,4 @@ -import keyMirror from 'keymirror'; +import keyMirror from "keymirror"; const FacebookConstants = { FACEBOOK_INITIALIZED: null, @@ -9,6 +9,6 @@ const FacebookConstants = { FACEBOOK_LOGGED_OUT: null, FACEBOOK_SIGN_IN_DISCONNECT: null, IMAGE_UPLOADED: null, -} +}; -module.exports = keyMirror(FacebookConstants) \ No newline at end of file +module.exports = keyMirror(FacebookConstants); diff --git a/src/js/constants/VoterConstants.js b/src/js/constants/VoterConstants.js index c057319fc..59bf55008 100644 --- a/src/js/constants/VoterConstants.js +++ b/src/js/constants/VoterConstants.js @@ -1,4 +1,4 @@ -const keyMirror = require('keymirror'); +const keyMirror = require("keymirror"); module.exports = keyMirror({ VOTER_LOCATION_RETRIEVE: null, diff --git a/src/js/constants/VoterGuideConstants.js b/src/js/constants/VoterGuideConstants.js index eb761ee30..241d923e7 100644 --- a/src/js/constants/VoterGuideConstants.js +++ b/src/js/constants/VoterGuideConstants.js @@ -1,4 +1,4 @@ -const keyMirror = require('keymirror'); +const keyMirror = require("keymirror"); module.exports = keyMirror({ // Voter guide and Organization Constants VOTER_GUIDES_TO_FOLLOW: null, diff --git a/src/js/dispatcher/AppDispatcher.js b/src/js/dispatcher/AppDispatcher.js index 82cbbfd0c..555af7b67 100644 --- a/src/js/dispatcher/AppDispatcher.js +++ b/src/js/dispatcher/AppDispatcher.js @@ -1,2 +1,2 @@ -var Dispatcher = require('flux').Dispatcher; +var Dispatcher = require("flux").Dispatcher; module.exports = new Dispatcher(); diff --git a/src/js/dispatcher/FacebookDispatcher.js b/src/js/dispatcher/FacebookDispatcher.js index aead3ecfd..5e55ac876 100644 --- a/src/js/dispatcher/FacebookDispatcher.js +++ b/src/js/dispatcher/FacebookDispatcher.js @@ -1,5 +1,5 @@ -import {Dispatcher} from 'flux'; +import {Dispatcher} from "flux"; const FacebookDispatcher = new Dispatcher(); -module.exports = FacebookDispatcher; \ No newline at end of file +module.exports = FacebookDispatcher; diff --git a/src/js/index.js b/src/js/index.js index 67efda2a5..5b211c9e2 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -81,7 +81,7 @@ if (!Object.assign) Object.assign = React.__spread; VoterStore.getVoterObject( (err, voter) => { if (err) handleVoterError(err); - renderApp(true, voter) + renderApp(true, voter); }); @@ -93,4 +93,4 @@ if (!Object.assign) Object.assign = React.__spread; } -}()) \ No newline at end of file +}()); diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 932282c86..493916aa3 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -1,33 +1,32 @@ -import BallotStore from '../stores/BallotStore'; -import { createStore } from '../utils/createStore'; -import { get } from '../utils/service'; -import {shallowClone} from '../utils/object-utils'; +import BallotStore from "../stores/BallotStore"; +import { createStore } from "../utils/createStore"; +import {shallowClone} from "../utils/object-utils"; -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const VoterGuideConstants = require('../constants/VoterGuideConstants'); +const AppDispatcher = require("../dispatcher/AppDispatcher"); +const VoterGuideConstants = require("../constants/VoterGuideConstants"); const cookies = require("../utils/cookies"); -const request = require('superagent'); -const web_app_config = require('../config'); +const request = require("superagent"); +const web_app_config = require("../config"); let _organization_store = {}; let _organization_list = []; // A summary of all organizations (list of organization we_vote_id's) let _voter_guide_store = {}; -let _voter_guide_list = []; // A summary of all voter_guides (list of organization we_vote_id's) +//let _voter_guide_list = []; // A summary of all voter_guides (list of organization we_vote_id's) let _voter_guides_to_follow_order = []; let _voter_guides_to_follow_list = []; // A summary of all voter guides to follow (list of voter guide we_vote_id's) -let _voter_guides_to_ignore_list = []; // A summary of voter guides to follow (list of voter guide we_vote_id's) +//let _voter_guides_to_ignore_list = []; // A summary of voter guides to follow (list of voter guide we_vote_id's) let _voter_guides_followed_order = []; let _voter_guides_followed_list = []; // A summary of voter guides already followed (list of voter guide we_vote_id's) -const MEASURE = 'MEASURE'; +const MEASURE = "MEASURE"; function printErr (err) { console.error(err); } -//.query({ ballot_item_we_vote_id: 'wv01cand2968' }) -//.query({ kind_of_ballot_item: 'CANDIDATE' }) +//.query({ ballot_item_we_vote_id: "wv01cand2968" }) +//.query({ kind_of_ballot_item: "CANDIDATE" }) function retrieveVoterGuidesToFollowList () { return new Promise( (resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}voterGuidesToFollowRetrieve/`) @@ -39,21 +38,21 @@ function retrieveVoterGuidesToFollowList () { if (err || !res.body.success) reject(err || res.body.status); - console.log('retrieveVoterGuidesToFollowList SUCCESS'); + console.log("retrieveVoterGuidesToFollowList SUCCESS"); resolve(res.body); }) - ) + ); } function addVoterGuidesToFollowToVoterGuideStore (data) { - console.log('ENTERING addVoterGuidesToFollowToVoterGuideStore'); + console.log("ENTERING addVoterGuidesToFollowToVoterGuideStore"); data.voter_guides.forEach( item => { _voter_guide_store[item.we_vote_id] = shallowClone(item); _voter_guides_to_follow_order.push(item.we_vote_id); _voter_guides_to_follow_list.push(item.we_vote_id); _organization_list.push(item.organization_we_vote_id); // To be retrieved in retrieveOrganizations }); - console.log('addVoterGuidesToFollowToVoterGuideStore SUCCESS'); + console.log("addVoterGuidesToFollowToVoterGuideStore SUCCESS"); return data; } @@ -69,7 +68,7 @@ function retrieveVoterGuidesFollowedList () { console.log("Reached out to retrieveVoterGuidesFollowedList"); resolve(res.body); }) - ) + ); } function addVoterGuidesFollowedToVoterGuideStore (data) { @@ -88,7 +87,7 @@ function retrieveOrganizations (data) { var organizations_count = 0; return new Promise ( (resolve, reject) => _organization_list - .forEach( we_vote_id => request + .forEach(we_vote_id => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationRetrieve/`) .withCredentials() .query({ organization_we_vote_id: we_vote_id }) @@ -102,7 +101,7 @@ function retrieveOrganizations (data) { organizations_count ++; if (organizations_count === _organization_list.length) { - console.log('retrieveOrganizations FOUND ALL'); + console.log("retrieveOrganizations FOUND ALL"); resolve(data); } }) @@ -121,7 +120,7 @@ function retrieveOrganizationsFollowedList () { resolve(res.body); }) - ) + ); } function addOrganizationsFollowedToStore (data) { @@ -134,7 +133,7 @@ function addOrganizationsFollowedToStore (data) { } function followOrganization (we_vote_id) { - console.log('followOrganization: ' + we_vote_id + ', id: ' + _organization_store[we_vote_id].organization_id); + console.log("followOrganization: " + we_vote_id + ", id: " + _organization_store[we_vote_id].organization_id); return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationFollow/`) .withCredentials() @@ -154,7 +153,7 @@ function followOrganization (we_vote_id) { } function ignoreOrganization (we_vote_id) { - console.log('ignoreOrganization: ' + we_vote_id); + console.log("ignoreOrganization: " + we_vote_id); return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationFollowIgnore/`) .withCredentials() @@ -174,7 +173,7 @@ function ignoreOrganization (we_vote_id) { } function stopFollowingOrganization (we_vote_id) { - console.log('stopFollowingOrganization: ' + we_vote_id); + console.log("stopFollowingOrganization: " + we_vote_id); return new Promise((resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationStopFollowing/`) .withCredentials() @@ -196,7 +195,7 @@ function stopFollowingOrganization (we_vote_id) { //const VoterGuideAPIWorker = { // voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success) { // return get({ -// endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', +// endpoint: "voterBallotItemsRetrieveFromGoogleCivic", // query: {text_for_map_search}, success: success || defaultSuccess // }); // } @@ -211,8 +210,8 @@ const VoterGuideStore = createStore({ initialize: function (callback) { var getItems = this.getOrderedVoterGuides.bind(this); - if (!callback || typeof callback !== 'function') - throw new Error('initialize must be called with callback'); + if (!callback || typeof callback !== "function") + throw new Error("initialize must be called with callback"); // Do we have Voter Guide data stored in the browser? if (Object.keys(_voter_guides_to_follow_list).length) @@ -237,8 +236,8 @@ const VoterGuideStore = createStore({ console.log("ENTERED initializeGuidesFollowed"); var getFollowedItems = this.getOrderedVoterGuidesFollowed.bind(this); - if (!callback || typeof callback !== 'function') - throw new Error('initialize must be called with callback'); + if (!callback || typeof callback !== "function") + throw new Error("initialize must be called with callback"); // Do we have Voter Guide data stored in the browser? if (Object.keys(_voter_guides_followed_list).length) { @@ -265,7 +264,7 @@ const VoterGuideStore = createStore({ var temp = []; _voter_guides_to_follow_order.forEach(we_vote_id => temp .push(shallowClone(_voter_guide_store[we_vote_id])) - ) + ); return temp; }, @@ -278,7 +277,7 @@ const VoterGuideStore = createStore({ var temp = []; _voter_guides_followed_order.forEach(we_vote_id => temp .push(shallowClone(_voter_guide_store[we_vote_id])) - ) + ); return temp; }, @@ -328,6 +327,6 @@ AppDispatcher.register( action => { VoterGuideStore.emitChange(); break; } -}) +}); export default VoterGuideStore; diff --git a/src/js/utils/createStore.js b/src/js/utils/createStore.js index 92e979f54..72571838a 100644 --- a/src/js/utils/createStore.js +++ b/src/js/utils/createStore.js @@ -1,7 +1,7 @@ -import EventEmitter from 'events'; -import assign from 'object-assign'; +import EventEmitter from "events"; +import assign from "object-assign"; -const CHANGE_EVENT = 'change'; +const CHANGE_EVENT = "change"; const MAX_LISTENERS = 300; /** @@ -9,17 +9,17 @@ const MAX_LISTENERS = 300; * @param {Object} spec object to mixin * @return {Store} DataStore Object */ -export function createStore(mixin) { +export function createStore (mixin) { const store = assign({}, EventEmitter.prototype, { - emitChange() { + emitChange () { this.emit(CHANGE_EVENT); }, - addChangeListener(callback) { + addChangeListener (callback) { this.on(CHANGE_EVENT, callback); }, - removeChangeListener(callback) { + removeChangeListener (callback) { this.removeListener(CHANGE_EVENT, callback); } diff --git a/src/js/utils/promise-utils.js b/src/js/utils/promise-utils.js index 9379df5b3..0478db082 100644 --- a/src/js/utils/promise-utils.js +++ b/src/js/utils/promise-utils.js @@ -1,12 +1,12 @@ export function factory ( promiseFactory ) { - if ( promiseFactory instanceof Array !== true ) throw new Error('wrong type'); + if (promiseFactory instanceof Array !== true) throw new Error("wrong type"); var firstPromiseFn = promiseFactory.shift(); - if ( firstPromiseFn instanceof Function !== true ) throw new Error('first promise is not function'); + if ( firstPromiseFn instanceof Function !== true ) throw new Error("first promise is not function"); promiseFactory.reduce( function (curr, next) { - if ( next instanceof Function !== true ) throw new Error('next is not a function'); + if ( next instanceof Function !== true ) throw new Error("next is not a function"); return curr.then( function (data) { return new Promise(next.bind(data)); }); From c8d1ac5b2cf6f9b3530b771b2f4a9e2280622f36 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Thu, 18 Feb 2016 15:39:52 -0800 Subject: [PATCH 48/92] Formatting clean up for 'npm test' eslint src --- src/js/stores/BallotStore.js | 166 +++++++++++++++---------------- src/js/stores/VoterGuideStore.js | 2 +- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 4bf28788f..a3e1ace77 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -1,16 +1,16 @@ -import { get } from '../utils/service'; -import { createStore } from '../utils/createStore'; -import { shallowClone } from '../utils/object-utils'; +import { get } from "../utils/service"; +import { createStore } from "../utils/createStore"; +import { shallowClone } from "../utils/object-utils"; -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const BallotConstants = require('../constants/BallotConstants'); +const AppDispatcher = require("../dispatcher/AppDispatcher"); +const BallotConstants = require("../constants/BallotConstants"); let _ballot_store = {}; let _ballot_order_ids = []; let _google_civic_election_id = null; -const MEASURE = 'MEASURE'; +const MEASURE = "MEASURE"; function defaultSuccess (res) { // console.warn(res); @@ -31,25 +31,25 @@ function ballotItemIsMeasure (we_vote_id) { const BallotAPIWorker = { voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { return get({ - endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', + endpoint: "voterBallotItemsRetrieveFromGoogleCivic", query: { text_for_map_search }, success: success || defaultSuccess }); }, candidatesRetrieve: function (office_we_vote_id, success ) { - return get({ endpoint: 'candidatesRetrieve', query: { office_we_vote_id }, + return get({ endpoint: "candidatesRetrieve", query: { office_we_vote_id }, success: success || defaultSuccess }); }, // get the ballot items voterBallotItemsRetrieve: function ( success ) { - return get({ endpoint: 'voterBallotItemsRetrieve', + return get({ endpoint: "voterBallotItemsRetrieve", success: success || defaultSuccess }); }, positionOpposeCountForBallotItem: function (we_vote_id, success ) { return get({ - endpoint: 'positionOpposeCountForBallotItem', + endpoint: "positionOpposeCountForBallotItem", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -58,9 +58,9 @@ const BallotAPIWorker = { }, // get measure support an opposition - positionSupportCountForBallotItem: function (we_vote_id, success ) { + positionSupportCountForBallotItem: function (we_vote_id, success) { return get({ - endpoint: 'positionSupportCountForBallotItem', + endpoint: "positionSupportCountForBallotItem", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -68,9 +68,9 @@ const BallotAPIWorker = { }); }, - voterPositionRetrieve: function ( ballot_item_we_vote_id, success ) { + voterPositionRetrieve: function (ballot_item_we_vote_id, success) { return get({ - endpoint: 'voterPositionRetrieve', + endpoint: "voterPositionRetrieve", query: { ballot_item_we_vote_id: ballot_item_we_vote_id, kind_of_ballot_item: _ballot_store[ballot_item_we_vote_id].kind_of_ballot_item @@ -78,9 +78,9 @@ const BallotAPIWorker = { }); }, - voterStarStatusRetrieve: function ( we_vote_id, success ) { + voterStarStatusRetrieve: function (we_vote_id, success) { return get({ - endpoint: 'voterStarStatusRetrieve', + endpoint: "voterStarStatusRetrieve", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -88,9 +88,9 @@ const BallotAPIWorker = { }); }, - voterStarOnSave: function (we_vote_id, success ) { + voterStarOnSave: function (we_vote_id, success) { return get({ - endpoint: 'voterStarOnSave', + endpoint: "voterStarOnSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -98,9 +98,9 @@ const BallotAPIWorker = { }); }, - voterStarOffSave: function (we_vote_id, success ) { + voterStarOffSave: function (we_vote_id, success) { return get({ - endpoint: 'voterStarOffSave', + endpoint: "voterStarOffSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -108,10 +108,10 @@ const BallotAPIWorker = { }); }, - voterSupportingSave: function (we_vote_id, success ) { - console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); + voterSupportingSave: function (we_vote_id, success) { + console.log("voterSupportingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterSupportingSave', + endpoint: "voterSupportingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -120,9 +120,9 @@ const BallotAPIWorker = { }, voterStopSupportingSave: function (we_vote_id, success ) { - console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopSupportingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStopSupportingSave', + endpoint: "voterStopSupportingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -131,9 +131,9 @@ const BallotAPIWorker = { }, voterOpposingSave: function (we_vote_id, success ) { - console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterOpposingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterOpposingSave', + endpoint: "voterOpposingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -142,9 +142,9 @@ const BallotAPIWorker = { }, voterStopOpposingSave: function (we_vote_id, success ) { - console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopOpposingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStopOpposingSave', + endpoint: "voterStopOpposingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -163,8 +163,8 @@ const BallotStore = createStore({ var promiseQueue = []; var getOrderedBallotItems = this.getOrderedBallotItems.bind(this); - if (!callback || typeof callback !== 'function') - throw new Error('initialize must be called with callback'); + if (!callback || typeof callback !== "function") + throw new Error("initialize must be called with callback"); // Do we have the Ballot data stored in the browser? if (Object.keys(_ballot_store).length) @@ -173,45 +173,45 @@ const BallotStore = createStore({ else { BallotAPIWorker - .voterBallotItemsRetrieve () + .voterBallotItemsRetrieve() .then( (res) => { - addItemsToBallotStore ( + addItemsToBallotStore( res.ballot_item_list ); _ballot_order_ids = res.ballot_item_list.map( (ballot) => ballot.we_vote_id ); _google_civic_election_id = res.google_civic_election_id; - console.log( 'BallotStore:', _ballot_store ); - console.log( 'BallotOrder:', _ballot_order_ids ); + console.log( "BallotStore:", _ballot_store ); + console.log( "BallotOrder:", _ballot_order_ids ); _ballot_order_ids.forEach( we_vote_id => { - console.log('is', we_vote_id, 'measure?', ballotItemIsMeasure(we_vote_id)); + console.log("is", we_vote_id, "measure?", ballotItemIsMeasure(we_vote_id)); promiseQueue - .push ( + .push( BallotAPIWorker .voterStarStatusRetrieve(we_vote_id) - .then ( (res) => _ballot_store[we_vote_id].is_starred = res.is_starred ) + .then( (res) => _ballot_store[we_vote_id].is_starred = res.is_starred ) ); if ( ballotItemIsMeasure(we_vote_id) ) { promiseQueue - .push ( + .push( BallotAPIWorker .positionOpposeCountForBallotItem( we_vote_id ) ); promiseQueue - .push ( + .push( BallotAPIWorker .positionSupportCountForBallotItem( we_vote_id ) ); promiseQueue - .push ( + .push( BallotAPIWorker .voterPositionRetrieve( we_vote_id ) ); @@ -222,7 +222,7 @@ const BallotStore = createStore({ .push( BallotAPIWorker - .candidatesRetrieve ( we_vote_id ) + .candidatesRetrieve( we_vote_id ) .then( (response) => { var cand_list = _ballot_store [ response.office_we_vote_id @@ -232,13 +232,13 @@ const BallotStore = createStore({ .candidate_list .forEach( (candidate) => { var { we_vote_id: candidate_we_vote_id } = candidate; - cand_list . push (candidate_we_vote_id); + cand_list . push(candidate_we_vote_id); _ballot_store [ candidate_we_vote_id ] = shallowClone( candidate ); promiseQueue - .push ( + .push( BallotAPIWorker - .positionOpposeCountForBallotItem (candidate_we_vote_id) + .positionOpposeCountForBallotItem(candidate_we_vote_id) .then( (res) => _ballot_store [ candidate_we_vote_id @@ -247,9 +247,9 @@ const BallotStore = createStore({ ); promiseQueue - .push ( + .push( BallotAPIWorker - .positionSupportCountForBallotItem (candidate_we_vote_id) + .positionSupportCountForBallotItem(candidate_we_vote_id) .then( (res) => _ballot_store [ candidate_we_vote_id @@ -261,7 +261,7 @@ const BallotStore = createStore({ .push( BallotAPIWorker .voterStarStatusRetrieve(candidate_we_vote_id) - .then ( (res) => + .then( (res) => _ballot_store [ candidate_we_vote_id ] . is_starred = res.is_starred @@ -272,7 +272,7 @@ const BallotStore = createStore({ .push( BallotAPIWorker .voterPositionRetrieve(candidate_we_vote_id) - .then ( (res) => { + .then( (res) => { _ballot_store [ candidate_we_vote_id ] . is_oppose = res.is_oppose; @@ -291,11 +291,11 @@ const BallotStore = createStore({ }); // this function polls requests for complete status. - new Promise ( (resolve) => { + new Promise( (resolve) => { var counted = []; var count = 0; - var interval = setInterval ( () => { + var interval = setInterval( () => { res.ballot_item_list.forEach( (item) => { var { we_vote_id } = item; @@ -389,13 +389,13 @@ const BallotStore = createStore({ }, getCandidateByWeVoteId: function (candidate_we_vote_id) { - return shallowClone ( + return shallowClone( _ballot_store [ candidate_we_vote_id ] ) || null; }, getBallotItemByWeVoteId: function (ballot_item_we_vote_id) { - return shallowClone ( + return shallowClone( _ballot_store [ ballot_item_we_vote_id ] ) || null; }, @@ -433,7 +433,7 @@ const BallotStore = createStore({ * @param {string} we_vote_id identifier for lookup in stored * @return {Boolean} starred or not starred */ -function toggleStarState(we_vote_id) { +function toggleStarState (we_vote_id) { var item = _ballot_store[we_vote_id]; item.is_starred = ! item.is_starred; //console.log(_ballot_store[we_vote_id]); @@ -445,58 +445,58 @@ function toggleStarState(we_vote_id) { */ function setLocalSupportOnState(we_vote_id) { var item = _ballot_store[we_vote_id]; - //console.log('setLocalSupportOnState BEFORE, is_support:', item.is_support, ', supportCount', item.supportCount); - if (item.is_support != true) { + //console.log("setLocalSupportOnState BEFORE, is_support:", item.is_support, ", supportCount", item.supportCount); + if (item.is_support !== true) { // Cheat and increase the counter without hitting the API item.supportCount += 1; } item.is_support = true; - //console.log('setLocalSupportOnState AFTER, is_support:', item.is_support, ', supportCount', item.supportCount); + //console.log("setLocalSupportOnState AFTER, is_support:", item.is_support, ", supportCount", item.supportCount); return true; } /** * toggle the support state of a ballot item to off by its we_vote_id */ -function setLocalSupportOffState(we_vote_id) { +function setLocalSupportOffState (we_vote_id) { var item = _ballot_store[we_vote_id]; - //console.log('setLocalSupportOffState BEFORE, is_support:', item.is_support, ', supportCount', item.supportCount); - if (item.is_support == true) { + //console.log("setLocalSupportOffState BEFORE, is_support:", item.is_support, ", supportCount", item.supportCount); + if (item.is_support === true) { // Cheat and decrease the counter without hitting the API item.supportCount -= 1; } item.is_support = false; - //console.log('setLocalSupportOffState AFTER, is_support:', item.is_support, ', supportCount', item.supportCount); + //console.log("setLocalSupportOffState AFTER, is_support:", item.is_support, ", supportCount", item.supportCount); return true; } /** * toggle the oppose state of a ballot item to On by its we_vote_id */ -function setLocalOpposeOnState(we_vote_id) { +function setLocalOpposeOnState (we_vote_id) { var item = _ballot_store[we_vote_id]; - //console.log('setLocalOpposeOnState BEFORE, is_oppose:', item.is_oppose, ', opposeCount', item.opposeCount); - if (item.is_oppose != true) { + //console.log("setLocalOpposeOnState BEFORE, is_oppose:", item.is_oppose, ", opposeCount", item.opposeCount); + if (item.is_oppose !== true) { // Cheat and increase the counter without hitting the API item.opposeCount += 1; } item.is_oppose = true; - //console.log('setLocalOpposeOnState AFTER, is_oppose:', item.is_oppose, ', opposeCount', item.opposeCount); + //console.log("setLocalOpposeOnState AFTER, is_oppose:", item.is_oppose, ", opposeCount", item.opposeCount); return true; } /** * toggle the oppose state of a ballot item to Off by its we_vote_id */ -function setLocalOpposeOffState(we_vote_id) { +function setLocalOpposeOffState (we_vote_id) { var item = _ballot_store[we_vote_id]; - //console.log('setLocalOpposeOffState BEFORE, is_oppose:', item.is_oppose, ', opposeCount', item.opposeCount); - if (item.is_oppose == true) { + //console.log("setLocalOpposeOffState BEFORE, is_oppose:", item.is_oppose, ", opposeCount", item.opposeCount); + if (item.is_oppose === true) { // Cheat and decrease the counter without hitting the API item.opposeCount -= 1; } item.is_oppose = false; - //console.log('setLocalOpposeOffState AFTER, is_oppose:', item.is_oppose, ', opposeCount', item.opposeCount); + //console.log("setLocalOpposeOffState AFTER, is_oppose:", item.is_oppose, ", opposeCount", item.opposeCount); return true; } @@ -506,42 +506,42 @@ AppDispatcher.register( action => { switch (action.actionType) { case BallotConstants.VOTER_SUPPORTING_SAVE: BallotAPIWorker.voterSupportingSave( - we_vote_id, () => setLocalSupportOnState(we_vote_id) - && setLocalOpposeOffState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => setLocalSupportOnState(we_vote_id) && + setLocalOpposeOffState(we_vote_id) && + BallotStore.emitChange() ); break; case BallotConstants.VOTER_STOP_SUPPORTING_SAVE: BallotAPIWorker.voterStopSupportingSave( - we_vote_id, () => setLocalSupportOffState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => setLocalSupportOffState(we_vote_id) && + BallotStore.emitChange() ); break; case BallotConstants.VOTER_OPPOSING_SAVE: BallotAPIWorker.voterOpposingSave( - we_vote_id, () => setLocalOpposeOnState(we_vote_id) - && setLocalSupportOffState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => setLocalOpposeOnState(we_vote_id) && + setLocalSupportOffState(we_vote_id) && + BallotStore.emitChange() ); break; case BallotConstants.VOTER_STOP_OPPOSING_SAVE: BallotAPIWorker.voterStopOpposingSave( - we_vote_id, () => setLocalOpposeOffState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => setLocalOpposeOffState(we_vote_id) && + BallotStore.emitChange() ); break; case BallotConstants.VOTER_STAR_ON_SAVE: BallotAPIWorker .voterStarOnSave( - we_vote_id, () => toggleStarState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => toggleStarState(we_vote_id) && + BallotStore.emitChange() ); break; case BallotConstants.VOTER_STAR_OFF_SAVE: BallotAPIWorker .voterStarOffSave( - we_vote_id, () => toggleStarState(we_vote_id) - && BallotStore.emitChange() + we_vote_id, () => toggleStarState(we_vote_id) && + BallotStore.emitChange() ); break; } diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 493916aa3..06d5d92fd 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -86,7 +86,7 @@ function addVoterGuidesFollowedToVoterGuideStore (data) { function retrieveOrganizations (data) { var organizations_count = 0; - return new Promise ( (resolve, reject) => _organization_list + return new Promise( (resolve, reject) => _organization_list .forEach(we_vote_id => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}organizationRetrieve/`) .withCredentials() From c273c59c10978eddf418749a5986f326455f6c7b Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Thu, 18 Feb 2016 15:41:46 -0800 Subject: [PATCH 49/92] Cleanup --- src/js/actions/BallotActions.js | 14 -------------- src/js/components/Ballot/PositionItem.jsx | 5 +++-- src/js/components/Ballot/PositionList.jsx | 5 +++-- src/js/components/ItemActionBar2.jsx | 5 +++-- src/js/routes/Ballot/Candidate.jsx | 4 ++-- src/js/stores/BallotStore.js | 9 ++++++--- src/js/utils/service.js | 2 +- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index e301a8b4b..2923cf3a6 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -47,20 +47,6 @@ module.exports = { }); }, - voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE - AppDispatcher.dispatch({ - actionType: BallotConstants.VOTER_SUPPORTING_SAVE, - we_vote_id - }); - }, - - voterSupportingSave: function (we_vote_id) { // VOTER_SUPPORTING_SAVE - AppDispatcher.dispatch({ - actionType: BallotConstants.VOTER_SUPPORTING_SAVE, - we_vote_id - }); - }, - voterStopSupportingSave: function (we_vote_id) { // VOTER_STOP_SUPPORTING_SAVE AppDispatcher.dispatch({ actionType: BallotConstants.VOTER_STOP_SUPPORTING_SAVE, diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 985a2d05e..f2af3ccf6 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -14,12 +14,13 @@ export default class PositionItem extends Component { } componentDidMount () { + this.changeListener = this._onChange.bind(this); PositionStore.retrievePositionByWeVoteId(this.props.position_we_vote_id); - PositionStore.addChangeListener(this._onChange.bind(this)); + PositionStore.addChangeListener(this.changeListener); } componentWillUnmount () { - PositionStore.removeChangeListener(this._onChange.bind(this)); + PositionStore.removeChangeListener(this.changeListener); } _onChange () { diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index 076488198..bb4451c94 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -14,11 +14,12 @@ export default class PositionList extends Component { componentDidMount (){ BallotStore.fetchCandidatePositions(this.props.we_vote_id); - BallotStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + BallotStore.addChangeListener(this.changeListener); } componentWillUnmount (){ - BallotStore.removeChangeListener(this._onChange.bind(this)); + BallotStore.removeChangeListener(this.changeListener); } _onChange (){ diff --git a/src/js/components/ItemActionBar2.jsx b/src/js/components/ItemActionBar2.jsx index fecb78567..ee9183169 100644 --- a/src/js/components/ItemActionBar2.jsx +++ b/src/js/components/ItemActionBar2.jsx @@ -23,11 +23,12 @@ export default class ItemActionBar2 extends Component { } componentDidMount () { - BallotStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + BallotStore.addChangeListener(this.changeListener); } componentWillUnmount () { - BallotStore.removeChangeListener(this._onChange.bind(this)); + BallotStore.removeChangeListener(this.changeListener); } _onChange () { diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 9766f1343..2884c9935 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -24,8 +24,8 @@ export default class Candidate extends Component { } componentDidMount(){ - - BallotStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + BallotStore.addChangeListener(this.changeListener); var candidate = BallotStore.getOrFetchCandidateByWeVoteId(this.props.params.we_vote_id); if (candidate) { this.setState({ candidate: candidate }); diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 86dbf8c93..90d3f2007 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -112,13 +112,14 @@ const BallotAPIWorker = { return get({ endpoint: 'voterStarStatusRetrieve', query: { - ballot_item_we_vote_id: we_vote_id, + ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item }, success: success || defaultSuccess }); }, voterStarOnSave: function (we_vote_id, success ) { + console.log('voterStarOnSave, we_vote_id:, ', we_vote_id); return get({ endpoint: 'voterStarOnSave', query: { @@ -129,6 +130,7 @@ const BallotAPIWorker = { }, voterStarOffSave: function (we_vote_id, success ) { + console.log('voterStarOffSave, we_vote_id:, ', we_vote_id); return get({ endpoint: 'voterStarOffSave', query: { @@ -250,7 +252,7 @@ const BallotStore = createStore({ BallotAPIWorker .candidatesRetrieve ( we_vote_id ) .then( (response) => { - var office_display_name = _ballot_store[response.office_we_vote_id]['ballot_item_display_name']; + var office_display_name = _ballot_store[response.office_we_vote_id].ballot_item_display_name; var cand_list = _ballot_store [ response.office_we_vote_id ] . candidate_list = []; @@ -388,7 +390,7 @@ const BallotStore = createStore({ }, fetchCandidateStarStatus: function ( we_vote_id){ - BallotAPIWorker.voterStarStatusRetrieve(we_vote_id).then( res =>{ + BallotAPIWorker.voterStarStatusRetrieve(we_vote_id).then( (res) =>{ BallotActions.candidateItemRetrieved(we_vote_id, 'is_starred', 'is_starred', res); }); }, @@ -534,6 +536,7 @@ function addCandidateToStore (res) { function toggleStarState(we_vote_id) { var item = _ballot_store[we_vote_id]; item.is_starred = ! item.is_starred; + console.log(item.is_starred) return true; } diff --git a/src/js/utils/service.js b/src/js/utils/service.js index 8715aaabc..aca78ee5b 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -47,7 +47,7 @@ export function get (options) { opts.url = url.resolve(opts.baseUrl, opts.endpoint); // We add voter_device_id to all endpoint calls - opts.query["voter_device_id"] = cookies.getItem("voter_device_id"); + opts.query.voter_device_id = cookies.getItem("voter_device_id"); return new Promise( (resolve, reject) => new request.Request("GET", opts.url) .accept(opts.dataType) From a74d0b0eb88e395b03c24b621ce2e558280d6952 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Thu, 18 Feb 2016 19:25:51 -0800 Subject: [PATCH 50/92] Reduce syntax eslint warnings --- src/js/actions/PositionActions.js | 6 +- src/js/components/Ballot/PositionItem.jsx | 4 +- src/js/constants/PositionConstants.js | 2 +- src/js/routes/Ballot/Candidate.jsx | 25 +----- src/js/stores/BallotStore.js | 99 +++++++++++------------ src/js/stores/PositionStore.js | 28 +++---- 6 files changed, 67 insertions(+), 97 deletions(-) diff --git a/src/js/actions/PositionActions.js b/src/js/actions/PositionActions.js index 9d72a49f4..4f92f8ac0 100644 --- a/src/js/actions/PositionActions.js +++ b/src/js/actions/PositionActions.js @@ -1,6 +1,6 @@ -'use strict'; -var AppDispatcher = require('../dispatcher/AppDispatcher'); -var PositionConstants = require('../constants/PositionConstants'); +"use strict"; +var AppDispatcher = require("../dispatcher/AppDispatcher"); +var PositionConstants = require("../constants/PositionConstants"); module.exports = { diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index f2af3ccf6..56ecb79a8 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -42,9 +42,7 @@ export default class PositionItem extends Component {

      - - { this.props.speaker_label }
      {/* TODO icon-org-placeholder */} - + { this.props.speaker_label }

      {supportText} Yesterday at 7:18 PM

      diff --git a/src/js/constants/PositionConstants.js b/src/js/constants/PositionConstants.js index 16ea7fb3f..be517d31d 100644 --- a/src/js/constants/PositionConstants.js +++ b/src/js/constants/PositionConstants.js @@ -1,3 +1,3 @@ -module.exports = require('keymirror')({ +module.exports = require("keymirror")({ POSITION_RETRIEVED: null }); diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 2884c9935..d17e5477a 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -43,23 +43,6 @@ export default class Candidate extends Component { return (
      ); }; - var support_item; - if (this.props.support_on) { - support_item = 7 ; - } else { - support_item = 7 ; - } - - var oppose_item; - if (this.props.oppose_on) { - oppose_item = 3 ; - } else { - oppose_item = 3 ; - } - - var organization_we_vote_id; - organization_we_vote_id = "wv02org1111"; - return (
      @@ -81,7 +64,7 @@ export default class Candidate extends Component {
      */} -{candidate.hasOwnProperty('is_starred') ? + {candidate.hasOwnProperty('is_starred') ? @@ -105,11 +88,7 @@ export default class Candidate extends Component {

      - - { candidate.ballot_item_display_name } - + { candidate.ballot_item_display_name }

      Running for { candidate.office_display_name }

      diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index f4293bb51..c04adace7 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -2,9 +2,9 @@ import { get } from "../utils/service"; import { createStore } from "../utils/createStore"; import { shallowClone } from "../utils/object-utils"; -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const BallotConstants = require('../constants/BallotConstants'); -const BallotActions = require('../actions/BallotActions'); +const AppDispatcher = require("../dispatcher/AppDispatcher"); +const BallotConstants = require("../constants/BallotConstants"); +const BallotActions = require("../actions/BallotActions"); let _ballot_store = {}; let _ballot_order_ids = []; @@ -13,7 +13,7 @@ let _google_civic_election_id = null; const MEASURE = "MEASURE"; function defaultSuccess (res) { - // console.warn(res); + console.warn(res); } function addItemsToBallotStore (ballot_item_list) { @@ -32,14 +32,14 @@ const BallotAPIWorker = { voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { return get({ - endpoint: 'voterBallotItemsRetrieveFromGoogleCivic', + endpoint: "voterBallotItemsRetrieveFromGoogleCivic", query: { text_for_map_search }, success: success || defaultSuccess }); }, candidatesRetrieve: function (office_we_vote_id, success ) { return get({ - endpoint: 'candidatesRetrieve', + endpoint: "candidatesRetrieve", query: { office_we_vote_id: office_we_vote_id }, success: success || defaultSuccess }); @@ -47,7 +47,7 @@ const BallotAPIWorker = { candidateRetrieve: function (we_vote_id, success ) { return get({ - endpoint: 'candidateRetrieve', + endpoint: "candidateRetrieve", query: { candidate_we_vote_id: we_vote_id }, success: success }); @@ -55,7 +55,7 @@ const BallotAPIWorker = { officeRetrieve: function (we_vote_id, success ) { return get({ - endpoint: 'officeRetrieve', + endpoint: "officeRetrieve", query: { office_we_vote_id: we_vote_id }, success: success }); @@ -63,13 +63,13 @@ const BallotAPIWorker = { // get the ballot items voterBallotItemsRetrieve: function ( success ) { - return get({ endpoint: 'voterBallotItemsRetrieve', + return get({ endpoint: "voterBallotItemsRetrieve", success: success || defaultSuccess }); }, - positionListForBallotItem : function( id, kind_of_ballot_item, success) { + positionListForBallotItem: function (id, kind_of_ballot_item, success) { return get({ - endpoint: 'positionListForBallotItem', + endpoint: "positionListForBallotItem", query: { ballot_item_id: id, kind_of_ballot_item: kind_of_ballot_item @@ -80,7 +80,7 @@ const BallotAPIWorker = { positionOpposeCountForBallotItem: function (we_vote_id, success ) { return get({ - endpoint: 'positionOpposeCountForBallotItem', + endpoint: "positionOpposeCountForBallotItem", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -91,7 +91,7 @@ const BallotAPIWorker = { // get measure support an opposition positionSupportCountForBallotItem: function (we_vote_id, success ) { return get({ - endpoint: 'positionSupportCountForBallotItem', + endpoint: "positionSupportCountForBallotItem", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -99,9 +99,9 @@ const BallotAPIWorker = { }); }, - voterPositionRetrieve: function ( ballot_item_we_vote_id, success ) { + voterPositionRetrieve: function ( ballot_item_we_vote_id, success ) { return get({ - endpoint: 'voterPositionRetrieve', + endpoint: "voterPositionRetrieve", query: { ballot_item_we_vote_id: ballot_item_we_vote_id, kind_of_ballot_item: _ballot_store[ballot_item_we_vote_id].kind_of_ballot_item @@ -111,7 +111,7 @@ const BallotAPIWorker = { voterStarStatusRetrieve: function ( we_vote_id, success ) { return get({ - endpoint: 'voterStarStatusRetrieve', + endpoint: "voterStarStatusRetrieve", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -120,9 +120,9 @@ const BallotAPIWorker = { }, voterStarOnSave: function (we_vote_id, success ) { - console.log('voterStarOnSave, we_vote_id:, ', we_vote_id); + console.log("voterStarOnSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStarOnSave', + endpoint: "voterStarOnSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -131,9 +131,9 @@ const BallotAPIWorker = { }, voterStarOffSave: function (we_vote_id, success ) { - console.log('voterStarOffSave, we_vote_id:, ', we_vote_id); + console.log("voterStarOffSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStarOffSave', + endpoint: "voterStarOffSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -142,9 +142,9 @@ const BallotAPIWorker = { }, voterSupportingSave: function (we_vote_id, success ) { - console.log('voterSupportingSave, we_vote_id:, ', we_vote_id); + console.log("voterSupportingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterSupportingSave', + endpoint: "voterSupportingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -153,9 +153,9 @@ const BallotAPIWorker = { }, voterStopSupportingSave: function (we_vote_id, success ) { - console.log('voterStopSupportingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopSupportingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStopSupportingSave', + endpoint: "voterStopSupportingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -164,9 +164,9 @@ const BallotAPIWorker = { }, voterOpposingSave: function (we_vote_id, success ) { - console.log('voterOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterOpposingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterOpposingSave', + endpoint: "voterOpposingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -175,9 +175,9 @@ const BallotAPIWorker = { }, voterStopOpposingSave: function (we_vote_id, success ) { - console.log('voterStopOpposingSave, we_vote_id:, ', we_vote_id); + console.log("voterStopOpposingSave, we_vote_id:, ", we_vote_id); return get({ - endpoint: 'voterStopOpposingSave', + endpoint: "voterStopOpposingSave", query: { ballot_item_id: _ballot_store[we_vote_id].id, kind_of_ballot_item: _ballot_store[we_vote_id].kind_of_ballot_item @@ -364,9 +364,9 @@ const BallotStore = createStore({ return candidate; } _ballot_store[candidate_we_vote_id] = {}; - _ballot_store[candidate_we_vote_id].kind_of_ballot_item = 'CANDIDATE'; + _ballot_store[candidate_we_vote_id].kind_of_ballot_item = "CANDIDATE"; - BallotAPIWorker.candidateRetrieve(candidate_we_vote_id, function(res){ + BallotAPIWorker.candidateRetrieve(candidate_we_vote_id, function (res){ BallotActions.candidateRetrieved(res); BallotStore.fetchCandidateDetails(candidate_we_vote_id); }); @@ -374,7 +374,7 @@ const BallotStore = createStore({ return _ballot_store[candidate_we_vote_id]; }, - fetchCandidateDetails: function(we_vote_id){ + fetchCandidateDetails: function (we_vote_id){ this.fetchCandidateStarStatus(we_vote_id); this.fetchCandidatePositions(we_vote_id); this.fetchCandidateOffice(we_vote_id); @@ -386,33 +386,33 @@ const BallotStore = createStore({ fetchCandidatePositions: function (we_vote_id){ BallotAPIWorker.positionListForBallotItem( _ballot_store[we_vote_id].id, - _ballot_store[we_vote_id].kind_of_ballot_item).then( res =>{ - BallotActions.candidateItemRetrieved(we_vote_id, 'position_list', 'position_list', res); + _ballot_store[we_vote_id].kind_of_ballot_item).then( res => { + BallotActions.candidateItemRetrieved(we_vote_id, "position_list", "position_list", res); }); }, fetchCandidateStarStatus: function ( we_vote_id){ BallotAPIWorker.voterStarStatusRetrieve(we_vote_id).then( (res) =>{ - BallotActions.candidateItemRetrieved(we_vote_id, 'is_starred', 'is_starred', res); + BallotActions.candidateItemRetrieved(we_vote_id, "is_starred", "is_starred", res); }); }, fetchCandidateOffice: function (we_vote_id){ var office_we_vote_id = _ballot_store[we_vote_id].contest_office_we_vote_id; BallotAPIWorker.officeRetrieve(office_we_vote_id).then( res => { - BallotActions.candidateItemRetrieved(we_vote_id, 'office_display_name', 'ballot_item_display_name', res); + BallotActions.candidateItemRetrieved(we_vote_id, "office_display_name", "ballot_item_display_name", res); }); }, fetchCandidateOpposeCount: function (we_vote_id){ - BallotAPIWorker.positionOpposeCountForBallotItem (we_vote_id).then( res => { - BallotActions.candidateItemRetrieved(we_vote_id, 'opposeCount', 'count', res); + BallotAPIWorker.positionOpposeCountForBallotItem(we_vote_id).then( res => { + BallotActions.candidateItemRetrieved(we_vote_id, "opposeCount", "count", res); }); }, - fetchCandidateSupportCount: function(we_vote_id){ - BallotAPIWorker.positionSupportCountForBallotItem (we_vote_id).then( res => { - BallotActions.candidateItemRetrieved(we_vote_id, 'supportCount', 'count', res); + fetchCandidateSupportCount: function (we_vote_id){ + BallotAPIWorker.positionSupportCountForBallotItem(we_vote_id).then( res => { + BallotActions.candidateItemRetrieved(we_vote_id, "supportCount", "count", res); }); }, @@ -498,9 +498,8 @@ const BallotStore = createStore({ getCandidateById: function (office_we_vote_id, candidate_we_vote_id) { return _ballot_store [ office_we_vote_id - ] . candidate_list.indexOf(candidate_we_vote_id) > -1 - - ? shallowClone( _ballot_store[candidate_we_vote_id] ) : undefined; + ] . candidate_list.indexOf(candidate_we_vote_id) > -1 ? + shallowClone( _ballot_store[candidate_we_vote_id] ) : undefined; }, getCandidatesForBallot: function (office_we_vote_id) { @@ -519,14 +518,14 @@ const BallotStore = createStore({ }); -function setCandidateDetail(we_vote_id, parameter, alias, payload) { +function setCandidateDetail (we_vote_id, parameter, alias, payload) { _ballot_store[we_vote_id][parameter] = payload[alias]; return true; } function addCandidateToStore (res) { _ballot_store[res.we_vote_id] = res; - _ballot_store[res.we_vote_id].kind_of_ballot_item = 'CANDIDATE'; + _ballot_store[res.we_vote_id].kind_of_ballot_item = "CANDIDATE"; return true; } @@ -538,14 +537,14 @@ function addCandidateToStore (res) { function toggleStarState (we_vote_id) { var item = _ballot_store[we_vote_id]; item.is_starred = ! item.is_starred; - console.log(item.is_starred) + console.log(item.is_starred); return true; } /** * toggle the support state of a ballot item to on by its we_vote_id */ -function setLocalSupportOnState(we_vote_id) { +function setLocalSupportOnState (we_vote_id) { var item = _ballot_store[we_vote_id]; //console.log("setLocalSupportOnState BEFORE, is_support:", item.is_support, ", supportCount", item.supportCount); if (item.is_support !== true) { @@ -608,13 +607,13 @@ BallotStore.dispatchToken = AppDispatcher.register( action => { switch (action.actionType) { case BallotConstants.CANDIDATE_DETAIL_RETRIEVED: - setCandidateDetail(action.we_vote_id, action.parameter, action.parameter_alias, action.payload) && + setCandidateDetail(action.we_vote_id, action.parameter, action.parameter_alias, action.payload); BallotStore.emitChange(); break; case BallotConstants.CANDIDATE_RETRIEVED: - addCandidateToStore(action.payload) - && BallotStore.emitChange(); + addCandidateToStore(action.payload); + BallotStore.emitChange(); break; case BallotConstants.VOTER_SUPPORTING_SAVE: diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js index 970541494..6347e1e69 100644 --- a/src/js/stores/PositionStore.js +++ b/src/js/stores/PositionStore.js @@ -1,23 +1,18 @@ -import { get } from '../utils/service'; -import { createStore } from '../utils/createStore'; -import { shallowClone } from '../utils/object-utils'; +import { get } from "../utils/service"; +import { createStore } from "../utils/createStore"; +import { shallowClone } from "../utils/object-utils"; -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const PositionConstants = require('../constants/PositionConstants'); -const PositionActions = require('../actions/PositionActions'); +const AppDispatcher = require("../dispatcher/AppDispatcher"); +const PositionConstants = require("../constants/PositionConstants"); +const PositionActions = require("../actions/PositionActions"); -const POSITION_CHANGE_EVENT = 'POSITION_CHANGE_EVENT'; var _position_store = {}; // All positions that have been fetched (by we_vote_ids) -function printErr (err) { - console.error(err); -} - const PositionAPIWorker = { positionRetrieve: function (we_vote_id, success) { return get({ - endpoint: 'positionRetrieve', + endpoint: "positionRetrieve", query: { position_we_vote_id: we_vote_id, }, @@ -31,7 +26,7 @@ const PositionStore = createStore({ retrievePositionByWeVoteId: function (we_vote_id){ PositionAPIWorker.positionRetrieve(we_vote_id, - function(res){ + function (res){ PositionActions.positionRetrieved(we_vote_id, res); }); }, @@ -47,13 +42,12 @@ function setLocalPosition (we_vote_id, position) { return true; } -AppDispatcher.register (action => { - var { we_vote_id } = action; +AppDispatcher.register(action => { switch (action.actionType) { case PositionConstants.POSITION_RETRIEVED: - setLocalPosition(action.we_vote_id, action.payload ) - && PositionStore.emitChange(); + setLocalPosition(action.we_vote_id, action.payload ); + PositionStore.emitChange(); break; } From 6a76373b6fd2e7422c0bf939352f05786bf21ef3 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Fri, 19 Feb 2016 01:53:58 -0500 Subject: [PATCH 51/92] Be sure to run npm install after pull --- package.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7e2847404..3126fe8e6 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "moment": "^2.11.2", "moment-timezone": "^0.5.0", "node-libs-browser": "^0.5.2", + "pre-commit": "^1.1.2", "react-addons-linked-state-mixin": "^0.14.3", "react-addons-test-utils": "~0.14.0", "react-bootstrap": "^0.28.1", @@ -74,6 +75,10 @@ "start": "gulp", "deps": "npm run deps:missing && npm run deps:extra", "deps:missing": "dependency-check package.json", - "deps:extra": "dependency-check package.json --extra --no-dev --ignore", "build:doc": "doctoc --github --title \"## Contents\" ./" - } + "deps:extra": "dependency-check package.json --extra --no-dev --ignore", + "build:doc": "doctoc --github --title \"## Contents\" ./" + }, + "pre-commit": [ + "test" + ] } From 2b7c1c589007313c75217bf952bf03dce0ceedb8 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Fri, 19 Feb 2016 14:01:32 -0800 Subject: [PATCH 52/92] Getting sign in by Facebook (and disconnect from Facebook) working without requiring page refresh. We can now retrieve updated voter object via ajax during Facebook signin process. Added some code documentation. --- src/js/Application.jsx | 43 +++- src/js/actions/BallotActions.js | 4 +- src/js/actions/FacebookActionCreators.js | 8 + src/js/actions/VoterActions.js | 3 + src/js/components/Facebook/FacebookSignIn.jsx | 3 +- src/js/constants/FacebookConstants.js | 1 + src/js/constants/VoterConstants.js | 1 + src/js/routes/More/SignIn.jsx | 83 ++++--- src/js/stores/BallotStore.js | 33 +-- src/js/stores/FacebookStore.js | 233 +++++++++++------- src/js/stores/VoterStore.js | 74 ++++-- 11 files changed, 317 insertions(+), 169 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index 12c52facf..4c12424b4 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -3,26 +3,51 @@ import Navigator from "./components/Navigator"; import MoreMenu from "./components/MoreMenu"; import Header from "./components/Header"; import SubHeader from "./components/SubHeader"; +import VoterStore from "./stores/VoterStore"; export default class Application extends Component { static propTypes = { children: PropTypes.element, - route: PropTypes.object + route: PropTypes.object, }; - constructor (props) { - super(props); - this.state = {}; + constructor(props) { + super(props); + + this.state = { + voter: { + } + }; } componentDidMount () { - // TODO: Figure out if voter is actually signed in... - this.setState({ is_signed_in: false }); + // console.log("Application.jsx, About to initialize VoterStore"); + VoterStore.getLocation( (err) => { + if (err) console.error("Application.jsx, Error initializing voter object", err); + VoterStore.getVoterObject( (_err, voter) => { + if (_err) console.error("Application.jsx, Error initializing voter object", err); + this.setState({voter}); + // console.log("Application.jsx, voter_object: ", voter) + }); + }); + // console.log("Application.jsx componentDidMount VoterStore.addChangeListener"); + VoterStore.addChangeListener(this._onVoterStoreChange.bind(this)); + } + + componentWillUnmount () { + // console.log("Application.jsx componentWillUnmount VoterStore.removeChangeListener"); + VoterStore.removeChangeListener(this._onVoterStoreChange.bind(this)); + } + + _onVoterStoreChange () { + this.setState({ + voter: VoterStore.getCachedVoterObject() + }); } render () { - var {is_signed_in} = this.state; - var {voter} = this.props.route; + var { voter } = this.state; + // console.log("In Application.jsx render, voter: ", voter) return
      @@ -38,7 +63,7 @@ export default class Application extends Component {
      - { is_signed_in ? : } + { voter.signed_in_personal ? : }
      { this.props.children } diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index 1a16564b6..29b8f02e0 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -22,7 +22,7 @@ module.exports = { // }); // }, - candidateItemRetrieved: function (we_vote_id, parameter, parameter_alias, payload){ + candidateItemRetrieved: function (we_vote_id, parameter, parameter_alias, payload) { // CANDIDATE_DETAIL_RETRIEVED AppDispatcher.dispatch({ actionType: BallotConstants.CANDIDATE_DETAIL_RETRIEVED, payload: payload, @@ -32,7 +32,7 @@ module.exports = { }); }, - candidateRetrieved: function (payload) { + candidateRetrieved: function (payload) { // CANDIDATE_RETRIEVED AppDispatcher.dispatch({ actionType: BallotConstants.CANDIDATE_RETRIEVED, payload: payload, diff --git a/src/js/actions/FacebookActionCreators.js b/src/js/actions/FacebookActionCreators.js index e17f132a1..972e4364b 100644 --- a/src/js/actions/FacebookActionCreators.js +++ b/src/js/actions/FacebookActionCreators.js @@ -60,6 +60,14 @@ const FacebookActionCreators = { }); }); }, + // Dale considering the need for this here + //connectWithFacebook: () => { + // // Add connection between We Vote and Facebook + // FacebookDispatcher.dispatch({ + // actionType: FacebookConstants.FACEBOOK_SIGN_IN_CONNECT, + // data: true + // }); + //}, disconnectFromFacebook: () => { // Removing connection between We Vote and Facebook diff --git a/src/js/actions/VoterActions.js b/src/js/actions/VoterActions.js index 503131638..f8a9ce7d6 100644 --- a/src/js/actions/VoterActions.js +++ b/src/js/actions/VoterActions.js @@ -2,6 +2,8 @@ var AppDispatcher = require("../dispatcher/AppDispatcher"); var VoterConstants = require("../constants/VoterConstants"); +// In the stores, there are AppDispatcher blocks that listen for these actionType constants (ex/ VOTER_SUPPORTING_SAVE) +// When action calls one of these functions, we are telling the code in the AppDispatcher block to run module.exports = { ChangeLocation: function (location) { // VOTER_LOCATION_RETRIEVE AppDispatcher.dispatch({ @@ -15,5 +17,6 @@ module.exports = { actionType: VoterConstants.VOTER_RETRIEVE, we_vote_id }); + } }; diff --git a/src/js/components/Facebook/FacebookSignIn.jsx b/src/js/components/Facebook/FacebookSignIn.jsx index c6d2480ae..b13c6f3d2 100644 --- a/src/js/components/Facebook/FacebookSignIn.jsx +++ b/src/js/components/Facebook/FacebookSignIn.jsx @@ -1,5 +1,6 @@ import React from "react"; import FacebookActionCreators from "../../actions/FacebookActionCreators"; +import VoterActions from "../../actions/VoterActions"; class FacebookSignIn extends React.Component { constructor (props) { @@ -13,7 +14,7 @@ class FacebookSignIn extends React.Component { didClickFacebookLoginButton () { console.log("didClickFacebookLoginButton"); - FacebookActionCreators.login(); + FacebookActionCreators.login(); // We call FacebookActionCreators.connectWithFacebook() within login() } } diff --git a/src/js/constants/FacebookConstants.js b/src/js/constants/FacebookConstants.js index 09db88389..cccffa26b 100644 --- a/src/js/constants/FacebookConstants.js +++ b/src/js/constants/FacebookConstants.js @@ -7,6 +7,7 @@ const FacebookConstants = { FACEBOOK_RECEIVED_PICTURE: null, FACEBOOK_LOGGED_IN: null, FACEBOOK_LOGGED_OUT: null, + //FACEBOOK_SIGN_IN_CONNECT: null, // Dale exploring need for this FACEBOOK_SIGN_IN_DISCONNECT: null, IMAGE_UPLOADED: null, }; diff --git a/src/js/constants/VoterConstants.js b/src/js/constants/VoterConstants.js index 59bf55008..79b40e67f 100644 --- a/src/js/constants/VoterConstants.js +++ b/src/js/constants/VoterConstants.js @@ -1,6 +1,7 @@ const keyMirror = require("keymirror"); module.exports = keyMirror({ + VOTER_CHANGE_LOCATION: null, VOTER_LOCATION_RETRIEVE: null, VOTER_RETRIEVE: null }); diff --git a/src/js/routes/More/SignIn.jsx b/src/js/routes/More/SignIn.jsx index 958702840..fe253d5c0 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -1,61 +1,72 @@ -import React, { Component } from "react"; +import React, { Component, PropTypes } from "react"; import { Button, ButtonToolbar, Input } from "react-bootstrap"; import { Link } from "react-router"; -import FacebookActionCreators from '../../actions/FacebookActionCreators'; -import FacebookStore from '../../stores/FacebookStore'; -import FacebookDisconnect from '../../components/Facebook/FacebookDisconnect'; -import FacebookLogin from '../../components/Facebook/FacebookLogin'; -import FacebookLogout from '../../components/Facebook/FacebookLogout'; -import FacebookDownloadPicture from '../../components/Facebook/FacebookDownloadPicture'; -import FacebookPicture from '../../components/Facebook/FacebookPicture'; -import FacebookSignIn from '../../components/Facebook/FacebookSignIn'; -import Main from '../../components/Facebook/Main'; -import VoterStore from '../../stores/VoterStore'; +import FacebookActionCreators from "../../actions/FacebookActionCreators"; +import FacebookStore from "../../stores/FacebookStore"; +import FacebookDisconnect from "../../components/Facebook/FacebookDisconnect"; +import FacebookLogin from "../../components/Facebook/FacebookLogin"; +import FacebookLogout from "../../components/Facebook/FacebookLogout"; +import FacebookDownloadPicture from "../../components/Facebook/FacebookDownloadPicture"; +import FacebookPicture from "../../components/Facebook/FacebookPicture"; +import FacebookSignIn from "../../components/Facebook/FacebookSignIn"; +import Main from "../../components/Facebook/Main"; +import VoterStore from "../../stores/VoterStore"; {/* VISUAL DESIGN: tbd */} export default class SignIn extends Component { + static propTypes = { + children: PropTypes.object + }; + constructor(props) { super(props); this.state = { - voter_object: { + voter: { } }; } - componentDidMount() { + componentDidMount () { this.setState(this.getFacebookState()); //console.log("SignIn, About to initialize VoterStore"); - VoterStore.initialize((voter_object) => { - //console.log('SignIn: ', voter_object, 'voter_object is your object') - this.setState({voter_object}); + VoterStore.getLocation( (err) => { + if (err) console.error("FacebookStore.js, Error initializing voter object", err); + + VoterStore.getVoterObject( (_err, voter) => { + if (_err) console.error("FacebookStore.js, Error initializing voter object", err); + + this.setState({voter}); + // console.log("SignIn: ", voter, "voter is your object") + }); }); + FacebookActionCreators.initFacebook(); FacebookStore.addChangeListener(() => this._onFacebookChange()); - console.log('SignIn componentDidMount VoterStore.addChangeListener'); + // console.log("SignIn componentDidMount VoterStore.addChangeListener"); VoterStore.addChangeListener(this._onVoterStoreChange.bind(this)); } - componentWillUnmount() { + componentWillUnmount () { FacebookStore.removeChangeListener(this._onFacebookChange); - console.log('SignIn componentWillUnmount VoterStore.removeChangeListener'); + // console.log("SignIn componentWillUnmount VoterStore.removeChangeListener"); VoterStore.removeChangeListener(this._onVoterStoreChange.bind(this)); } _onVoterStoreChange () { this.setState({ - voter_object: VoterStore.getVoterObject() + voter: VoterStore.getCachedVoterObject() }); } - _onFacebookChange() { + _onFacebookChange () { this.setState(this.getFacebookState()); } - getFacebookState() { + getFacebookState () { return { accessToken: FacebookStore.accessToken, facebookIsLoggedIn: FacebookStore.loggedIn, @@ -70,14 +81,14 @@ export default class SignIn extends Component { } render() { - var { voter_object } = this.state; + var { voter } = this.state; return (
      -

      {voter_object.signed_in_personal ? My Account : Sign In}

      +

      {voter.signed_in_personal ? My Account : Sign In}

      - {voter_object.signed_in_facebook ? : } + {voter.signed_in_facebook ? : } {/*
      @@ -94,20 +105,24 @@ export default class SignIn extends Component {

      - {voter_object.signed_in_facebook ? : null} + {voter.signed_in_facebook ? : null}
      + {/* FOR DEBUGGING
      - signed_in_personal: {voter_object.signed_in_personal ? True : null}
      - signed_in_facebook: {voter_object.signed_in_facebook ? True : null}
      - signed_in_twitter: {voter_object.signed_in_twitter ? True : null}
      - we_vote_id: {voter_object.we_vote_id ? {voter_object.we_vote_id} : null}
      - email: {voter_object.email ? {voter_object.email} : null}
      - facebook_email: {voter_object.facebook_email ? {voter_object.facebook_email} : null}
      - first_name: {voter_object.first_name ? {voter_object.first_name} : null}
      - facebook_id: {voter_object.facebook_id ? {voter_object.facebook_id} : null}
      + signed_in_personal: {voter.signed_in_personal ? True : null}
      + signed_in_facebook: {voter.signed_in_facebook ? True : null}
      + signed_in_twitter: {voter.signed_in_twitter ? True : null}
      + we_vote_id: {voter.we_vote_id ? {voter.we_vote_id} : null}
      + email: {voter.email ? {voter.email} : null}
      + facebook_email: {voter.facebook_email ? {voter.facebook_email} : null}
      + first_name: {voter.first_name ? {voter.first_name} : null}
      + facebook_id: {voter.facebook_id ? {voter.facebook_id} : null}
      + */}
      + {/* FOR DEBUGGING
      + */}
      ); } diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index c04adace7..bce5a765a 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -16,18 +16,6 @@ function defaultSuccess (res) { console.warn(res); } -function addItemsToBallotStore (ballot_item_list) { - ballot_item_list.forEach( ballot_item => { - _ballot_store[ballot_item.we_vote_id] = shallowClone(ballot_item); - _ballot_store[ballot_item.we_vote_id].opposeCount = 0; - _ballot_store[ballot_item.we_vote_id].supportCount = 0; - }); -} - -function ballotItemIsMeasure (we_vote_id) { - return _ballot_store[we_vote_id].kind_of_ballot_item === MEASURE; -} - const BallotAPIWorker = { voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { @@ -518,6 +506,19 @@ const BallotStore = createStore({ }); +// These methods are used by the AppDispatcher code to change variables in the store +function addItemsToBallotStore (ballot_item_list) { + ballot_item_list.forEach( ballot_item => { + _ballot_store[ballot_item.we_vote_id] = shallowClone(ballot_item); + _ballot_store[ballot_item.we_vote_id].opposeCount = 0; + _ballot_store[ballot_item.we_vote_id].supportCount = 0; + }); +} + +function ballotItemIsMeasure (we_vote_id) { + return _ballot_store[we_vote_id].kind_of_ballot_item === MEASURE; +} + function setCandidateDetail (we_vote_id, parameter, alias, payload) { _ballot_store[we_vote_id][parameter] = payload[alias]; return true; @@ -601,10 +602,10 @@ function setLocalOpposeOffState (we_vote_id) { return true; } - +// This block is reacting to actions triggered in BallotActions.js. We update store variables, and then emitChange BallotStore.dispatchToken = AppDispatcher.register( action => { - var { we_vote_id } = action; - switch (action.actionType) { + var { we_vote_id } = action; + switch (action.actionType) { case BallotConstants.CANDIDATE_DETAIL_RETRIEVED: setCandidateDetail(action.we_vote_id, action.parameter, action.parameter_alias, action.payload); @@ -657,6 +658,8 @@ BallotStore.dispatchToken = AppDispatcher.register( action => { BallotStore.emitChange() ); break; + default: + break; } }); diff --git a/src/js/stores/FacebookStore.js b/src/js/stores/FacebookStore.js index 655b39288..40f471d3c 100644 --- a/src/js/stores/FacebookStore.js +++ b/src/js/stores/FacebookStore.js @@ -1,32 +1,85 @@ -import FacebookConstants from '../constants/FacebookConstants'; -import FacebookDispatcher from '../dispatcher/FacebookDispatcher'; -import VoterStore from '../stores/VoterStore'; -import {EventEmitter} from 'events'; -import service from '../utils/service'; +import { $ajax } from "../utils/service"; +import FacebookConstants from "../constants/FacebookConstants"; +import FacebookDispatcher from "../dispatcher/FacebookDispatcher"; +import VoterStore from "../stores/VoterStore"; +import {EventEmitter} from "events"; +import service from "../utils/service"; -const FACEBOOK_CHANGE_EVENT = 'FACEBOOK_CHANGE_EVENT'; +const cookies = require("../utils/cookies"); +const FACEBOOK_CHANGE_EVENT = "FACEBOOK_CHANGE_EVENT"; + +const FacebookAPIWorker = { + voterFacebookPhotoSave: function (photo_url, success ) { + console.log("FacebookAPIWorker.voterFacebookPhotoSave"); + return service.get({ + endpoint: "voterPhotoSave", + query: { + voter_device_id: cookies.getItem("voter_device_id"), + facebook_profile_image_url_https: photo_url + }, success + }); + }, + + facebookSignIn: function (facebook_id, facebook_email, callback) { + // console.log("In FacebookStore.js, FacebookAPIWorker.facebookSignIn, facebook_id: ", facebook_id); + return $ajax({ + type: "GET", + endpoint: "facebookSignIn", + data: { + facebook_id: facebook_id, + facebook_email: facebook_email + }, + success: (result) => { + callback(result); + }, + error: (err) => { + callback(err); + } + }); + }, + + /** + * Disconnect facebook from this account by removing the facebook_id from the db + * @param {String} voter_device_id will be passed + * @return {Boolean} Was the disconnection successful? + */ + facebookDisconnect: function (callback) { + // console.log("In FacebookStore.js, FacebookAPIWorker.facebookSignIn"); + if (callback instanceof Function === false) throw new Error("facebookDisconnect, missing callback function"); + + $ajax({ + type: "GET", + endpoint: "facebookDisconnect", + success: (response) => { + callback(response); + }, + error: (err) => callback(err) + }); + + } +}; class FacebookStore extends EventEmitter { - constructor() { + constructor () { super(); this.facebookAuthData = {}; this.faebookPictureData = {}; } - setFacebookAuthData(data) { + setFacebookAuthData (data) { this.facebookAuthData = data; this.emitChange(); } - get loggedIn() { + get loggedIn () { if (!this.facebookAuthData) { return; } - return this.facebookAuthData.status == 'connected'; + return this.facebookAuthData.status === "connected"; } - get userId() { + get userId () { if (!this.facebookAuthData || !this.facebookAuthData.authResponse) { return; } @@ -34,7 +87,7 @@ class FacebookStore extends EventEmitter { return this.facebookAuthData.authResponse.userID; } - get accessToken() { + get accessToken () { if (!this.facebookAuthData || !this.facebookAuthData.authResponse) { return; } @@ -42,7 +95,7 @@ class FacebookStore extends EventEmitter { return this.facebookAuthData.authResponse.accessToken; } - get facebookPictureUrl() { + get facebookPictureUrl () { if (!this.facebookPictureData || !this.facebookPictureData.url) { return; } @@ -50,11 +103,11 @@ class FacebookStore extends EventEmitter { return this.facebookPictureData.url; } - setFacebookPictureData(type, data) { + setFacebookPictureData (type, data) { this.facebookPictureStatus = type; if (data) { - this.facebookPictureData = data.data + this.facebookPictureData = data.data; } else { this.facebookPictureData = {}; } @@ -62,7 +115,7 @@ class FacebookStore extends EventEmitter { this.emitChange(); } - saveFacebookPictureData(data) { + saveFacebookPictureData (data) { if (data) { FacebookAPIWorker .voterFacebookPhotoSave( @@ -71,8 +124,10 @@ class FacebookStore extends EventEmitter { } } - saveFacebookAuthData() { + saveFacebookAuthData () { if (this.facebookAuthData) { + console.log("In FacebookStore.js, saveFacebookAuthData: ", this.facebookAuthData); + console.log("userID: ", this.facebookAuthData.authResponse.userID); FacebookAPIWorker .facebookSignIn( this.facebookAuthData.authResponse.userID, false, () => this.emit(FACEBOOK_CHANGE_EVENT) @@ -80,59 +135,68 @@ class FacebookStore extends EventEmitter { } } - disconnectFromFacebook() { + connectWithFacebook () { + if (this.facebookAuthData) { + // console.log("In FacebookStore.js, connectWithFacebook, this.facebookAuthData: ", this.facebookAuthData); + // console.log("userID: ", this.facebookAuthData.authResponse.userID); + FacebookAPIWorker + .facebookSignIn(this.facebookAuthData.authResponse.userID, false, () => { + // console.log("Call to FacebookAPIWorker.facebookSignIn has completed"); + this.emit(FACEBOOK_CHANGE_EVENT); + // Once we have connected to Facebook, grab a fresh version of the voter + VoterStore.getLocation( (err) => { + if (err) handleVoterError(err); + VoterStore.retrieveFreshVoterObject( (_err, voter_object) => { + if (_err) { + handleVoterError(_err); + } else { + // console.log("facebookStore.connectWithFacebook, voter: ", voter_object); + // Finally, update all components listening for changes in Voter Store + VoterStore.emitChange(); + } + }); + }); + } + ); + } + } + + disconnectFromFacebook () { FacebookAPIWorker .facebookDisconnect( - () => this.emit(FACEBOOK_CHANGE_EVENT) + () => { + // console.log("FacebookAPIWorker.facebookDisconnect has completed"); + this.emit(FACEBOOK_CHANGE_EVENT); + // Once we have disconnected from Facebook, grab a fresh version of the voter + VoterStore.getLocation( (err) => { + if (err) handleVoterError(err); + VoterStore.retrieveFreshVoterObject( (_err, voter_object) => { + if (_err) { + handleVoterError(_err); + } else { + // console.log("facebookStore.dispatchToken: FACEBOOK_SIGN_IN_DISCONNECT, voter: ", voter_object); + // Finally, update all components listening for changes in Voter Store + VoterStore.emitChange(); + } + }); + }); + } ); } - emitChange() { + emitChange () { this.emit(FACEBOOK_CHANGE_EVENT); } - addChangeListener(callback) { + addChangeListener (callback) { this.on(FACEBOOK_CHANGE_EVENT, callback); } - removeChangeListener(callback) { + removeChangeListener (callback) { this.removeListener(FACEBOOK_CHANGE_EVENT, callback); } } -const FacebookAPIWorker = { - voterFacebookPhotoSave: function (photo_url, success ) { - return service.get({ - endpoint: 'voterPhotoSave', - query: { - facebook_profile_image_url_https: photo_url - }, success - }); - }, - - facebookSignIn: function (facebook_id, facebook_email, success ) { - return service.get({ - endpoint: 'facebookSignIn', - query: { - facebook_id: facebook_id, - facebook_email: facebook_email - }, success - }); - }, - - /** - * Disconnect facebook from this account by removing the facebook_id from the db - * @param {String} voter_device_id will be passed - * @return {Boolean} Was the disconnection successful? - */ - facebookDisconnect: function (success ) { - return service.get({ - endpoint: 'facebookDisconnect', - data: success - }); - } -}; - function sleep (milliseconds) { var start = new Date().getTime(); for (var i = 0; i < 1e7; i++) { @@ -140,63 +204,50 @@ function sleep (milliseconds) { } } +function handleVoterError (err) { + console.error("FacebookStore.js, Error initializing voter object", err); +} + // initialize the store as a singleton const facebookStore = new FacebookStore(); facebookStore.dispatchToken = FacebookDispatcher.register((action) => { - if (action.actionType == FacebookConstants.FACEBOOK_INITIALIZED) { + if (action.actionType === FacebookConstants.FACEBOOK_INITIALIZED) { facebookStore.setFacebookAuthData(action.data); } - if (action.actionType == FacebookConstants.FACEBOOK_LOGGED_IN) { - console.log("FACEBOOK_LOGGED_IN"); - facebookStore.setFacebookAuthData(action.data); - facebookStore.saveFacebookAuthData(); - - sleep(3000); - console.log("FACEBOOK_LOGGED_IN: Trying to retrieve fresh voter data"); - var voter = VoterStore.getVoterObject(); - console.log('Before ', voter); - VoterStore.voterRetrieveFresh((voter_object) => { - console.log('facebookStore : FACEBOOK_INITIALIZED', voter_object); - }); - - sleep(3000); - var voter = VoterStore.getVoterObject(); - console.log('After ', voter); + if (action.actionType === FacebookConstants.FACEBOOK_LOGGED_IN) { + // console.log("facebookStore, actionType: FACEBOOK_LOGGED_IN, action.data: ", action.data); + facebookStore.setFacebookAuthData(action.data); // TODO set this up so following functions are dependent + facebookStore.connectWithFacebook(); } - if (action.actionType == FacebookConstants.FACEBOOK_LOGGED_OUT) { + if (action.actionType === FacebookConstants.FACEBOOK_LOGGED_OUT) { facebookStore.setFacebookAuthData(action.data); } - if (action.actionType == FacebookConstants.FACEBOOK_SIGN_IN_DISCONNECT) { - console.log("FACEBOOK_SIGN_IN_DISCONNECT"); - facebookStore.disconnectFromFacebook(); + //if (action.actionType === FacebookConstants.FACEBOOK_SIGN_IN_CONNECT) { + // // Dale exploring need for this vs. 'FACEBOOK_LOGGED_IN'? + // console.log("facebookStore.dispatchToken: FACEBOOK_SIGN_IN_CONNECT"); + // facebookStore.connectWithFacebook(); + //} - - sleep(3000); - console.log("FACEBOOK_SIGN_IN_DISCONNECT: Trying to retrieve fresh voter data"); - var voter = VoterStore.getVoterObject(); - console.log('Before ', voter); - VoterStore.voterRetrieveFresh(voter.we_vote_id); - - sleep(3000); - var voter = VoterStore.getVoterObject(); - console.log('After ', voter); + if (action.actionType === FacebookConstants.FACEBOOK_SIGN_IN_DISCONNECT) { + // console.log("facebookStore.dispatchToken: FACEBOOK_SIGN_IN_DISCONNECT"); + facebookStore.disconnectFromFacebook(); } - if (action.actionType == FacebookConstants.FACEBOOK_GETTING_PICTURE) { - facebookStore.setFacebookPictureData(action.actionType, action.data) + if (action.actionType === FacebookConstants.FACEBOOK_GETTING_PICTURE) { + facebookStore.setFacebookPictureData(action.actionType, action.data); } - if (action.actionType == FacebookConstants.FACEBOOK_RECEIVED_PICTURE) { + if (action.actionType === FacebookConstants.FACEBOOK_RECEIVED_PICTURE) { console.log("FACEBOOK_RECEIVED_PICTURE"); facebookStore.setFacebookPictureData(action.actionType, action.data); facebookStore.saveFacebookPictureData(action.data); facebookStore.saveFacebookAuthData(); - // VoterStore.updateVoterData(); // This would be nice to get working + // We could use facebookStore.connectWithFacebook() here } -}) +}); module.exports = facebookStore; diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 77bf7ee35..2fbb1c169 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -13,21 +13,6 @@ let _location = cookies.getItem("location"); let _voter_id = cookies.getItem("voter_id"); let _voter = {}; -function _setVoterId (id) { - _voter_id = id; - cookies.setItem("voter_id", id, Infinity); -} - -function _setDeviceId (id) { - _voter_device_id = id; - cookies.setItem("voter_device_id", id, Infinity); -} - -function _setLocation (location) { - _location = location; - cookies.setItem("location", location, Infinity); -} - const VoterStore = createStore({ hasDeviceId: function () { @@ -69,6 +54,31 @@ const VoterStore = createStore({ }); }, + /** + * get a refreshed version of the RAW JSON object from api.wevoteusa and merge + * it with other calculated values object + * @param {Function} callback (err, voter-object) + */ + retrieveFreshVoterObject: function (callback) { + return $ajax({ + endpoint: "voterRetrieve", + success: (res) => { + _voter = assign({}, _voter, res); + callback(null, assign({}, _voter)); + }, + error: (err) => { + callback(err); + } + }); + }, + + /** + * get the RAW JSON object cached in the local _voter variable + */ + getCachedVoterObject: function () { + return _voter; + }, + /** * initialize the voter store with data, if no data * and callback with the voter items @@ -208,12 +218,42 @@ const VoterStore = createStore({ } }); -AppDispatcher.register( action => { +// These methods are used by the AppDispatcher code to change variables in the store +function _setLocalVoterInStore (voter) { + _voter = voter; +} + +function _setVoterId (id) { + _voter_id = id; + cookies.setItem("voter_id", id, Infinity); +} + +function _setDeviceId (id) { + _voter_device_id = id; + cookies.setItem("voter_device_id", id, Infinity); +} + +function _setLocation (location) { + _location = location; + cookies.setItem("location", location, Infinity); +} + +// This block is reacting to actions triggered in BallotActions.js. We update store variables, and then emitChange +VoterStore.dispatchToken = AppDispatcher.register( action => { switch (action.actionType) { - case VoterConstants.VOTER_CHANGE_LOCATION: // ChangeLocation + case VoterConstants.VOTER_CHANGE_LOCATION: // _setLocation + // Update the variable value in the store + _setLocation(action.location); VoterStore.emitChange(); break; + + case VoterConstants.VOTER_RETRIEVE: // _setLocation + // Update the variable value in the store + _setLocalVoterInStore(action.voter); + VoterStore.emitChange(); + break; + default: break; } From 241ee2eb1739ea64706d8ea9c53d0ce10a4742dc Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Fri, 19 Feb 2016 18:54:07 -0500 Subject: [PATCH 53/92] a lot of eslint fixes to follow coding standards --- src/js/components/Ballot/BallotItem.jsx | 24 ++++----- src/js/components/Ballot/CandidateDetail.jsx | 2 +- src/js/components/Ballot/CandidateItem.jsx | 32 ++++++----- src/js/components/Ballot/CandidateList.jsx | 12 ++--- src/js/components/Ballot/Measure.jsx | 16 +++--- src/js/components/Ballot/Opinion.jsx | 9 ++-- src/js/components/Ballot/PositionItem.jsx | 57 ++++++++++---------- src/js/components/Ballot/PositionList.jsx | 26 ++++----- 8 files changed, 80 insertions(+), 98 deletions(-) diff --git a/src/js/components/Ballot/BallotItem.jsx b/src/js/components/Ballot/BallotItem.jsx index 57e73cb82..71bb3e43f 100644 --- a/src/js/components/Ballot/BallotItem.jsx +++ b/src/js/components/Ballot/BallotItem.jsx @@ -1,11 +1,11 @@ -import React, { Component, PropTypes } from 'react'; -import BallotActions from '../../actions/BallotActions'; -import BallotStore from '../../stores/BallotStore'; -import CandidateList from '../../components/Ballot/CandidateList'; -import Measure from '../../components/Ballot/Measure'; -import StarAction from '../../components/StarAction'; - -const TYPES = require('keymirror')({ +import React, { Component, PropTypes } from "react"; +import BallotActions from "../../actions/BallotActions"; +import BallotStore from "../../stores/BallotStore"; +import CandidateList from "../../components/Ballot/CandidateList"; +import Measure from "../../components/Ballot/Measure"; +import StarAction from "../../components/StarAction"; + +const TYPES = require("keymirror")({ OFFICE: null, MEASURE: null }); @@ -41,10 +41,9 @@ export default class BallotItem extends Component { return BallotStore.getCandidatesForBallot(this.props.we_vote_id); } - render() { + render () { - return ( -
      + return
      { this.props.ballot_item_display_name } @@ -57,7 +56,6 @@ export default class BallotItem extends Component { { this.isMeasure() ? : } -
      - ); +
      ; } } diff --git a/src/js/components/Ballot/CandidateDetail.jsx b/src/js/components/Ballot/CandidateDetail.jsx index 58b5c2d45..cf223beeb 100644 --- a/src/js/components/Ballot/CandidateDetail.jsx +++ b/src/js/components/Ballot/CandidateDetail.jsx @@ -1,4 +1,4 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component, PropTypes } from "react"; export default class CandidateDetail extends Component { // some of these PropTypes can diff --git a/src/js/components/Ballot/CandidateItem.jsx b/src/js/components/Ballot/CandidateItem.jsx index 2c5a9e156..e311d5da4 100644 --- a/src/js/components/Ballot/CandidateItem.jsx +++ b/src/js/components/Ballot/CandidateItem.jsx @@ -1,10 +1,10 @@ -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; +import React, { Component, PropTypes } from "react"; +import { Link } from "react-router"; -import BallotActions from '../../actions/BallotActions'; +import BallotActions from "../../actions/BallotActions"; import BallotStore from "../../stores/BallotStore"; -import StarAction from '../../components/StarAction'; -import ItemActionbar from '../../components/ItemActionbar'; +import StarAction from "../../components/StarAction"; +import ItemActionbar from "../../components/ItemActionbar"; export default class Candidate extends Component { static propTypes = { @@ -34,7 +34,7 @@ export default class Candidate extends Component { BallotStore.addChangeListener(this.changeListener); } - componentWillUnmount() { + componentWillUnmount () { BallotStore.removeChangeListener(this.changeListener); } @@ -45,7 +45,7 @@ export default class Candidate extends Component { }); } - render() { + render () { let { we_vote_id, ballot_item_display_name, @@ -60,27 +60,26 @@ export default class Candidate extends Component { } var support_emphasis = "support-emphasis-small"; - if (this.state.supportCount == 1) { + if (this.state.supportCount === 1) { support_emphasis = "support-emphasis-medium"; } else if (this.state.supportCount > 1) { - if ((this.state.supportCount - this.state.opposeCount) > 0) { + if (this.state.supportCount - this.state.opposeCount > 0) { support_emphasis = "support-emphasis-large"; } else { - // if there isn't more support than opposition, then tone down the emphasis to medium + // if there isn"t more support than opposition, then tone down the emphasis to medium support_emphasis = "support-emphasis-medium"; } } - return ( -
      + return
      -
      +
      + style={candidate_photo_url ? {} : {height: "95px"}}> {/* adding inline style to img until Rob can style... */} { @@ -88,7 +87,7 @@ export default class Candidate extends Component { candidate-photo : @@ -121,7 +120,6 @@ export default class Candidate extends Component {
      -
      - ); +
      ; } } diff --git a/src/js/components/Ballot/CandidateList.jsx b/src/js/components/Ballot/CandidateList.jsx index adfeb9766..8f1274bab 100644 --- a/src/js/components/Ballot/CandidateList.jsx +++ b/src/js/components/Ballot/CandidateList.jsx @@ -1,20 +1,18 @@ -import React, { Component, PropTypes } from 'react'; -import CandidateItem from '../../components/Ballot/CandidateItem'; +import React, { Component, PropTypes } from "react"; +import CandidateItem from "../../components/Ballot/CandidateItem"; export default class CandidateList extends Component { static propTypes = { children: PropTypes.array.isRequired }; - constructor(props) { + constructor (props) { super(props); } render () { - return ( -
      + return
      { this.props.children.map( (child) => ) } -
      - ); +
      ; } } diff --git a/src/js/components/Ballot/Measure.jsx b/src/js/components/Ballot/Measure.jsx index d59e31f8a..1dc91bc2e 100644 --- a/src/js/components/Ballot/Measure.jsx +++ b/src/js/components/Ballot/Measure.jsx @@ -1,9 +1,9 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component, PropTypes } from "react"; -import BallotActions from '../../actions/BallotActions'; -import BallotStore from '../../stores/BallotStore'; +import BallotActions from "../../actions/BallotActions"; +import BallotStore from "../../stores/BallotStore"; -import ItemActionbar from '../../components/ItemActionbar'; +import ItemActionbar from "../../components/ItemActionbar"; export default class Measure extends Component { static propTypes = { @@ -41,9 +41,8 @@ export default class Measure extends Component { ); } - render() { - return ( -
      + render () { + return

      { this.state.supportCount } support (more) @@ -53,7 +52,6 @@ export default class Measure extends Component {

      -
      - ); +
      ; } } diff --git a/src/js/components/Ballot/Opinion.jsx b/src/js/components/Ballot/Opinion.jsx index a26ec7866..bea77f735 100644 --- a/src/js/components/Ballot/Opinion.jsx +++ b/src/js/components/Ballot/Opinion.jsx @@ -1,10 +1,7 @@ -import React, { Component } from 'react'; +import React, { Component } from "react"; export default class Opinion extends Component { - render() { - return ( -
      -
      - ); + render () { + return
      ; } } diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 56ecb79a8..320e05a70 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -1,7 +1,6 @@ -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; -import PositionActions from '../../actions/PositionActions'; -import PositionStore from '../../stores/PositionStore'; +import React, { Component, PropTypes } from "react"; +import { Link } from "react-router"; +import PositionStore from "../../stores/PositionStore"; export default class PositionItem extends Component { static propTypes = { @@ -10,7 +9,7 @@ export default class PositionItem extends Component { constructor (props) { super(props); - this.state = { position: {} }; + this.state = { position: {} }; } componentDidMount () { @@ -30,32 +29,30 @@ export default class PositionItem extends Component { render () { var position = this.state.position; var supportText = position.is_oppose ? "Opposes" : "Supports"; - return ( -
      - {/* One organization's Position on this Candidate */} -
    • -
      -
      - - - -
      -
      -

      - { this.props.speaker_label }
      -

      -

      {supportText} Yesterday at 7:18 PM

      -
      + return
      + {/* One organization"s Position on this Candidate */} +
    • +
      +
      + + +
      -
      - {position.statement_text} +
      +

      + { this.props.speaker_label }
      +

      +

      {supportText} Yesterday at 7:18 PM

      - {/* Likes coming in a later version -
      - 23 Likes
      - */} -
    • -
      - ); +
      +
      + {position.statement_text} +
      + {/* Likes coming in a later version +
      + 23 Likes
      + */} + +
      ; } } diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index bb4451c94..68c0712ed 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -1,6 +1,6 @@ -import React, { Component, PropTypes } from 'react'; -import BallotStore from '../../stores/BallotStore'; -import PositionItem from './PositionItem'; +import React, { Component, PropTypes } from "react"; +import BallotStore from "../../stores/BallotStore"; +import PositionItem from "./PositionItem"; export default class PositionList extends Component { static propTypes = { @@ -28,18 +28,14 @@ export default class PositionList extends Component { render () { if (!this.state.position_list){ - return ( -
      - ); + return
      ; } - return ( -
        - { this.state.position_list.map( item => - ) - } -
      - ); + return
        + { this.state.position_list.map( item => + ) + } +
      ; } } From 5a0c3afdd2055bea35d2e3497ae38f018d1c0754 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Fri, 19 Feb 2016 21:39:46 -0800 Subject: [PATCH 54/92] Altered several interface elements to say "Coming Soon" to help prepare for upcoming MVP. Commented out very noisy console.warn in BallotStore.js --- src/js/components/MoreMenu.jsx | 2 ++ src/js/routes/Activity.jsx | 6 +++-- src/js/routes/AddFriends.jsx | 7 +++++ src/js/routes/Connect.jsx | 8 ++++++ src/js/routes/More/OpinionsFollowed.jsx | 5 ++-- src/js/routes/Opinions.jsx | 35 ++++++++++++++----------- src/js/routes/Requests.jsx | 13 ++++++--- src/js/stores/BallotStore.js | 2 +- 8 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/js/components/MoreMenu.jsx b/src/js/components/MoreMenu.jsx index db38c6c81..e9133a710 100644 --- a/src/js/components/MoreMenu.jsx +++ b/src/js/components/MoreMenu.jsx @@ -58,11 +58,13 @@ export default class MoreMenu extends Component { {/*
    • Terms & Policies
    • */}
    • Admin
    • + {/* {this.props.signed_in_personal ?
    • Sign Out
    • : } + */}
    diff --git a/src/js/routes/Activity.jsx b/src/js/routes/Activity.jsx index 6007e95d3..812457b1f 100644 --- a/src/js/routes/Activity.jsx +++ b/src/js/routes/Activity.jsx @@ -18,8 +18,10 @@ export default class Activity extends Component { render() { return (
    -
    - Activity Feed Coming Soon +
    +

    Activity Feed
    + Coming Soon

    +

    See the latest endorsements and news.

    ); diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx index 97fb0656f..ffb99b640 100644 --- a/src/js/routes/AddFriends.jsx +++ b/src/js/routes/AddFriends.jsx @@ -18,6 +18,10 @@ export default class AddFriends extends Component { return (
    +

    Add Friends
    + Coming Soon

    +

    You will be able to ask your friends for their opinions on how to vote.

    + {/* Still to be implemented

    Add Friends


    @@ -28,8 +32,11 @@ export default class AddFriends extends Component { These friends will see what you support, oppose, and which opinions you follow. We never sell email addresses.
    + */}
    + {/* Still to be implemented + */}
    ); } diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 55d746f33..7a8ea4f85 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -35,11 +35,19 @@ export default class Connect extends Component {

    Follow More Opinions

    + + + +

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.
    +

    + + {/*
    + */}
    ); diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index bc2562200..8b2a94302 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -24,10 +24,11 @@ export default class OpinionsFollowed extends Component { return (
    -

    Opinions I Follow

    +

    Opinions I'm Following

    + {/*
    - + */}
    { this.state.voter_guide_followed_list ? diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index a5336db4d..fc46e0807 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -25,22 +25,27 @@ export default class Opinions extends Component {

    More Opinions I Can Follow

    + {/*
    - - These organizations and public figures have opinions about items on your - ballot. Click the 'Follow' button to pay attention to them. - -
    - { - this.state.voter_guide_list ? - this.state.voter_guide_list.map( item => - - ) : (
    - -

    Loading ... One Moment

    -
    ) - } + placeholder="Search by name or twitter handle." /> + */} +
    +
    +
    + These organizations and public figures have opinions about items on your + ballot. Click the 'Follow' button to pay attention to them. + +
    + { + this.state.voter_guide_list ? + this.state.voter_guide_list.map( item => + + ) : (
    + +

    Loading ... One Moment

    +
    ) + } +
    diff --git a/src/js/routes/Requests.jsx b/src/js/routes/Requests.jsx index ec9b02234..030c78687 100644 --- a/src/js/routes/Requests.jsx +++ b/src/js/routes/Requests.jsx @@ -16,25 +16,30 @@ export default class RequestsPage extends Component { return (
    +

    Friend Requests
    + Coming Soon

    +

    Friends will be able to reach out to you so you can collaborate on how to vote.

    + {/*

    Friend Requests

    • - {/* TODO icon-person-placeholder */} + Janet Smith
    • - {/* TODO icon-person-placeholder */} + Will Rogers
    • - {/* TODO icon-person-placeholder */} + Andrea Moed
    • - {/* TODO icon-person-placeholder */} + Amy Muller
    + */}
    ); diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index bce5a765a..2bca70316 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -13,7 +13,7 @@ let _google_civic_election_id = null; const MEASURE = "MEASURE"; function defaultSuccess (res) { - console.warn(res); + // console.warn(res); } const BallotAPIWorker = { From f14cd7c7548f41b4872e56b9a6926ada7735849e Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 20 Feb 2016 06:22:16 -0500 Subject: [PATCH 55/92] Update UI margins, paddings, and headings --- .../components/VoterGuide/VoterGuideItem.jsx | 10 ++-- src/js/routes/Activity.jsx | 6 +-- src/js/routes/AddFriends.jsx | 6 +-- src/js/routes/Connect.jsx | 8 ++-- src/js/routes/More/About.jsx | 9 ++-- src/js/routes/More/OpinionsFollowed.jsx | 4 +- src/js/routes/More/SignIn.jsx | 4 +- src/js/routes/Opinions.jsx | 46 +++++++++---------- src/js/routes/Requests.jsx | 6 +-- src/js/routes/Settings/Location.jsx | 21 +++++---- src/sass/layout/_boxmodel.scss | 5 ++ src/sass/layout/_mediaquery.scss | 8 +++- src/sass/vendor/_vendors.scss | 4 ++ 13 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/js/components/VoterGuide/VoterGuideItem.jsx b/src/js/components/VoterGuide/VoterGuideItem.jsx index 6e75e4a74..acd9ab0c3 100644 --- a/src/js/components/VoterGuide/VoterGuideItem.jsx +++ b/src/js/components/VoterGuide/VoterGuideItem.jsx @@ -47,10 +47,14 @@ export default class VoterGuideItem extends Component { render() { return ( -
    +
    -
    -   +
    + +   { this.props.voter_guide_display_name }
    -
    -

    Activity Feed
    - Coming Soon

    +
    +

    Activity Feed

    +

    Coming Soon

    See the latest endorsements and news.

    diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx index ffb99b640..95414d0ed 100644 --- a/src/js/routes/AddFriends.jsx +++ b/src/js/routes/AddFriends.jsx @@ -17,9 +17,9 @@ export default class AddFriends extends Component { render() { return (
    -
    -

    Add Friends
    - Coming Soon

    +
    +

    Add Friends

    +

    Coming Soon

    You will be able to ask your friends for their opinions on how to vote.

    {/* Still to be implemented

    Add Friends

    diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 7a8ea4f85..2282fccc9 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -26,17 +26,17 @@ export default class Connect extends Component { }; return (
    -
    +

    Add Friends

    - +

    Friends can see what you support and oppose. We never sell emails.

    Follow More Opinions

    - +

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.

    @@ -44,7 +44,7 @@ export default class Connect extends Component { {/* - +
    */} diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index eabcc094b..d1b04eca3 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -15,13 +15,13 @@ export default class About extends Component { render() { return (
    -
    -

    About We Vote

    +
    +

    About We Vote

    We Vote USA is nonprofit and nonpartisan. For more information, please visit www.WeVoteUSA.org.

    -

    Acknowledgements

    +

    Acknowledgements

    We are grateful for these organizations that are critical to our work.

    @@ -39,7 +39,8 @@ export default class About extends Component {

    Special thanks to our team of volunteers. - Thank you everyone! (This is a list of volunteers who have contributed 10 or more hours, in rough order of hours contributed.)
    + Thank you everyone! (This is a list of volunteers who have contributed 10 or more hours, in rough order of hours contributed.)

    +

    Dale McGrew - Oakland, CA
    Jenifer Fernandez Ancona - Oakland, CA
    Rob Simpson - Vienna, VA
    diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index 8b2a94302..6364109d8 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -23,8 +23,8 @@ export default class OpinionsFollowed extends Component { render() { return (

    -
    -

    Opinions I'm Following

    +
    +

    Opinions I'm Following

    {/*
    diff --git a/src/js/routes/More/SignIn.jsx b/src/js/routes/More/SignIn.jsx index fe253d5c0..e5397b023 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -85,8 +85,8 @@ export default class SignIn extends Component { return (
    -
    -

    {voter.signed_in_personal ? My Account : Sign In}

    +
    +

    {voter.signed_in_personal ? My Account : Sign In}

    {voter.signed_in_facebook ? : } {/* diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index fc46e0807..2db5513e9 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -22,33 +22,29 @@ export default class Opinions extends Component { render() { return ( -
    -
    -

    More Opinions I Can Follow

    - {/* - - */} -
    -
    -
    - These organizations and public figures have opinions about items on your - ballot. Click the 'Follow' button to pay attention to them. +
    +
    +

    More Opinions I Can Follow

    + {/* + + */} +

    These organizations and public figures have opinions about items on your + ballot. Click the 'Follow' button to pay attention to them.

    -
    - { - this.state.voter_guide_list ? - this.state.voter_guide_list.map( item => - - ) : (
    - -

    Loading ... One Moment

    -
    ) - } +
    + { + this.state.voter_guide_list ? + this.state.voter_guide_list.map( item => + + ) : (
    + +

    Loading ... One Moment

    +
    ) + } +
    +
    -
    -
    -
    ); } } diff --git a/src/js/routes/Requests.jsx b/src/js/routes/Requests.jsx index 030c78687..f2dc07fdd 100644 --- a/src/js/routes/Requests.jsx +++ b/src/js/routes/Requests.jsx @@ -15,9 +15,9 @@ export default class RequestsPage extends Component { render() { return (
    -
    -

    Friend Requests
    - Coming Soon

    +
    +

    Friend Requests

    +

    Coming Soon

    Friends will be able to reach out to you so you can collaborate on how to vote.

    {/*

    Friend Requests

    diff --git a/src/js/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index 6c074184a..22bd6e3b4 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -35,10 +35,10 @@ export default class Location extends Component { var { location } = this.state; return
    -
    -

    +
    +

    Change Location -

    +

    Please enter the address (or just the city) where you registered to @@ -52,14 +52,17 @@ export default class Location extends Component { name="address" value={location} className="form-control" - defaultValue="Oakland, CA" /> + defaultValue="Oakland, CA" + /> - - +
    + + - + +
    ; diff --git a/src/sass/layout/_boxmodel.scss b/src/sass/layout/_boxmodel.scss index d47b092a5..b91f1d294 100644 --- a/src/sass/layout/_boxmodel.scss +++ b/src/sass/layout/_boxmodel.scss @@ -28,6 +28,11 @@ .fluff-loose--top { padding-top: 1.5em; } + +.fluff-full1 { + padding: 1em; +} + @media all and (min-width: 480px) { .fluff-loose--top { padding-top: .8em; diff --git a/src/sass/layout/_mediaquery.scss b/src/sass/layout/_mediaquery.scss index 7412ab295..f06074915 100644 --- a/src/sass/layout/_mediaquery.scss +++ b/src/sass/layout/_mediaquery.scss @@ -29,7 +29,7 @@ display: inline-block; height: 45px; left: auto; - right: 100px; + right: 175px; top: 0; .separate-top { @@ -78,4 +78,10 @@ margin: 0 auto; width: 960px; } + + .headroom--pinned { + margin: 0 auto; + max-width: 958px; + width: 958px; + } } diff --git a/src/sass/vendor/_vendors.scss b/src/sass/vendor/_vendors.scss index 957599d6e..1c3b90f07 100644 --- a/src/sass/vendor/_vendors.scss +++ b/src/sass/vendor/_vendors.scss @@ -21,6 +21,10 @@ border-radius: 0; } +.well-bg--light { + background-color: #fff; +} + .row { margin-left: 0; margin-right: 0; From c8daf4fc5e502dd39574c874a0b61c54f4122927 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Sat, 20 Feb 2016 08:49:14 -0800 Subject: [PATCH 56/92] Add instructions to view server logs to README --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 04c5c72ef..1e8459de1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # README for We Vote WebApp -![WeVoteUS](wevotelogo.png) +![WeVoteUS](wevotelogo.png) [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) @@ -44,7 +44,7 @@ Confirm the versions of your main packages are >= to these versions: (WebAppEnv) $ npm -v 3.3.12 - + ## Overview of Process 1. Fork the repository to your GitHub repo. @@ -74,9 +74,34 @@ You should be able to visit WebApp here: http://localhost:3000 +## Viewing server logs + If you would like to see the server logs while developing, you can follow these steps. + +### Get an SSH key + 1. If you already have an SSH key you'd like to use, skip to the next section. Otherwise... + 2. Open a terminal on your local computer and enter the following: ssh-keygen -t rsa -C "your_email@example.com" ... + 3. Just press to accept the default location and file name. ... + 4. Enter, and re-enter, a passphrase when prompted. ... + 5. You're done! + +### Get authorized and log in: + 1. Run cd ~/.ssh/ at the command line. + 3. Copy the contents of the file id_rsa.pub (your public key). + 4. Email the key to servers@wevoteusa.org. + 5. You will receive an email with a command to login (ssh @ec2-52-32-204-163.us-west-2.compute.amazonaws.com) + 6. Run the command, if prompted 'Are you sure you want to continue?' Type yes. + 7. You should now be logged in. + +### Viewing Server Logs +1. To view server errors, run tail -F /var/log/wevote/wevoteserver.log +2. To view all the server activity, run tail -F /var/log/upstart/wevote-api.log +3. To only view activity that is coming from localhost:3000 on your computer, run tail -F /var/log/upstart/wevote-api.log | grep +4. Note: You can get your device_id by navigating to localhost:3000, opening chrome developer tools and finding the cookie labeled voter_device_id (under Resources > Cookies > Localhost) + + ## Using We Vote API server Locally -The default configuration connections to our live API server at: https://api.wevoteusa.org +The default configuration connections to our live API server at: https://api.wevoteusa.org If you would like to install the We Vote API server locally, start by reading the instructions[install WeVoteServer](https://github.com/wevote/WeVoteServer/blob/master/README_API_INSTALL.md) ## After Installation: Working with WebApp Day-to-Day From eff6f9a0fbe375e1affbc15ef6098840ba549f18 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 20 Feb 2016 14:11:44 -0500 Subject: [PATCH 57/92] #21 More menu slides in and out on mobile --- src/js/Application.jsx | 2 +- src/js/components/Header.jsx | 79 +++++++++++++++++++++++++++++--- src/sass/layout/_mediaquery.scss | 36 +++++++++++++++ 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/js/Application.jsx b/src/js/Application.jsx index 4c12424b4..6e40c68aa 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -65,7 +65,7 @@ export default class Application extends Component {
    { voter.signed_in_personal ? : }
    -
    +
    { this.props.children }
    diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 443f5f17d..7c2268c43 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -1,13 +1,23 @@ -import React, { Component } from "react"; +const web_app_config = require("../config"); +import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import Headroom from "react-headroom"; - +import LanguageSwitchNavigation from "../components/LanguageSwitchNavigation"; import VoterStore from "../stores/VoterStore"; export default class Header extends Component { + static propTypes = { + email: PropTypes.string, + first_name: PropTypes.string, + voter_photo_url: PropTypes.string, + signed_in_personal: PropTypes.bool + }; + constructor (props) { super(props); - this.state = {}; + this.state = { + visible: false + }; } componentDidMount () { @@ -17,16 +27,33 @@ export default class Header extends Component { }); } + show() { + var mainContainer = document.querySelector('.container-main'); + if (this.state.visible) { + document.addEventListener("click", this.setState({visible: false})); + mainContainer.style.opacity = 1; + } else { + document.addEventListener("click", this.setState({visible: true})); + mainContainer.style.opacity = .2; + } + } + render () { + var voter_image = ""; + if (this.props.voter_photo_url) { + voter_image = + } var {location} = this.state; return

    - - - + My Ballot

    + {/* The menu has to be reproduced for mobile */} +
    + {this.props.signed_in_personal ? + + : + +
      +
    • Sign In
    • +
    +

    +
    + } +
      + {/*
    • Print or Email Ballot
    • */} +
    • Opinions I'm Following
    • +
    • My Ballot Location
    • +
    • Account Settings
    • +
    + {/* +
      +
    • + +
    • +
    + */} +

    +
      +
    • About We Vote
    • + {/*
    • Terms & Policies
    • */} +
    • Admin
    • + {/* + {this.props.signed_in_personal ? +
    • Sign Out
    • + : + + } + */} +
    +
    ; } } diff --git a/src/sass/layout/_mediaquery.scss b/src/sass/layout/_mediaquery.scss index f06074915..57a8fa715 100644 --- a/src/sass/layout/_mediaquery.scss +++ b/src/sass/layout/_mediaquery.scss @@ -3,6 +3,9 @@ &--large { display: none; } + &--mobile { + display: none; + } } &-ballot { &--large { @@ -11,6 +14,39 @@ } } } +.icon { + &-hamburger { + &--mobile { + display: none; + } + } +} + +@media all and (max-width : 695px) { + .visible { + display: inline-block; + } + .device { + &-menu { + &--mobile { + position: fixed; + left: 0; + top: 43px; + transform: translate3d(-10px, 0, 0); + -webkit-transform: translate3d(-10px, 0, 0); + width: 90%; + z-index: 2; + } + } + } + .icon { + &-hamburger { + &--mobile { + display: inline-block; + } + } + } +} @media all and (min-width : 660px) { From b633b5ba858169e941a8d6d972a788428bd6d784 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 20 Feb 2016 14:42:00 -0500 Subject: [PATCH 58/92] #53 flip address with menu in header for space --- src/js/components/Header.jsx | 2 +- src/sass/layout/_header.scss | 6 ++++++ src/sass/layout/_mediaquery.scss | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 443f5f17d..ce1f584f9 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -29,7 +29,7 @@ export default class Header extends Component { My Ballot -
    ; } } diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index cc398ba92..b349afc9a 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -2,7 +2,6 @@ const web_app_config = require("../config"); import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import Headroom from "react-headroom"; -import LanguageSwitchNavigation from "../components/LanguageSwitchNavigation"; import VoterStore from "../stores/VoterStore"; export default class Header extends Component { @@ -23,86 +22,113 @@ export default class Header extends Component { componentDidMount () { VoterStore.getLocation( (err, location) => { if (err) console.error(err); + this.setState({ location }); + }); } - show() { - var mainContainer = document.querySelector('.container-main'); + show () { + var mainContainer = document.querySelector(".container-main"); if (this.state.visible) { document.addEventListener("click", this.setState({visible: false})); mainContainer.style.opacity = 1; } else { document.addEventListener("click", this.setState({visible: true})); - mainContainer.style.opacity = .2; + mainContainer.style.opacity = 0.2; } } render () { - var voter_image = ""; - if (this.props.voter_photo_url) { - voter_image = - } - var {location} = this.state; + var { location, visible } = this.state; + var { voter_photo_url: url, signed_in_personal: signedIn } = this.props; + // var image; + // + // if (url) + // image = ; - return
    - -
    -

    - - My Ballot -

    - -
    -
    - {/* The menu has to be reproduced for mobile */} -
    - {this.props.signed_in_personal ? - - : - -
      -
    • Sign In
    • -
    -

    -
    + const header = +
    + +
    +

    + + + My Ballot +

    + +
    +
    + {/* The menu has to be reproduced for mobile */} +
    + { signedIn ? : + +
      +
    • + + Sign In + +
    • +
    +

    +
    } -
      - {/*
    • Print or Email Ballot
    • */} -
    • Opinions I'm Following
    • -
    • My Ballot Location
    • -
    • Account Settings
    • -
    - {/* -
      -
    • - -
    • -
    - */} -

    -
      -
    • About We Vote
    • - {/*
    • Terms & Policies
    • */} -
    • Admin
    • +
        + {/*
      • Print or Email Ballot
      • */} +
      • + + Opinions I'm Following + +
      • +
      • + + My Ballot Location + +
      • +
      • + + Account Settings + +
      • +
      {/* - {this.props.signed_in_personal ? -
    • Sign Out
    • - : - - } +
        +
      • + +
      • +
      */} -
    -
    -
    ; +

    +
      +
    • + + About We Vote + +
    • + + {/*
    • Terms & Policies
    • */} + +
    • + + Admin + +
    • + + { signedIn ? +
    • + + Sign Out + +
    • : } + +
    +
    +
    ; + + return header; } } diff --git a/src/js/components/Navigator.jsx b/src/js/components/Navigator.jsx index 5d8a8cef9..7dd7ed580 100644 --- a/src/js/components/Navigator.jsx +++ b/src/js/components/Navigator.jsx @@ -1,53 +1,106 @@ -import React, { Component } from "react"; +import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; // import "stylesheets/main.scss"; +const links = { + ballot: function (active) { + var icon = "glyphicon glyphicon-list-alt glyphicon-line-adjustment font-footer_icon"; + + var jsx = + +
    + +
    + + Ballot + +
    + ; + + return jsx; + }, + + requests: function (active) { + var icon = "glyphicon glyphicon-inbox glyphicon-line-adjustment font-footer_icon"; + + var jsx = + +
    + + 10 + +
    + + Requests + +
    + ; + + return jsx; + }, + + connect: function (active) { + var icon = "glyphicon icon-icon-connect-1-3 font-footer_icon"; + + var jsx = + +
    + +
    + + Connect + +
    + ; + + return jsx; + }, + + activity: function (active) { + var icon = "glyphicon icon-icon-activity-1-4 font-footer_icon"; + + var jsx = + +
    + +
    + + Activity + +
    + ; + + return jsx; + } +}; + export default class Navigator extends Component { - render() { - return ( -
    -
    -
    -
    -
    - -
    - -
    - Ballot -
    - - -
    - - 10
    - Requests -
    - - -
    - -
    - Connect -
    - - -
    - -
    - Activity -
    - -
    -
    -
    + static propTypes = { + pathname: PropTypes.string + }; + + render () { + var { props: { pathname } } = this; + var { ballot, requests, connect, activity } = links; + + const navigator = +
    +
    +
    +
    +
    + {ballot(pathname === "/ballot")} + {requests(pathname === "/requests")} + {connect(pathname === "/connect")} + {activity(pathname === "/activity")} +
    +
    - ); +
    ; + + return navigator; + } } diff --git a/src/sass/components/_navigator.scss b/src/sass/components/_navigator.scss new file mode 100644 index 000000000..807aab822 --- /dev/null +++ b/src/sass/components/_navigator.scss @@ -0,0 +1,15 @@ +$icon-color-base: #999999; +$icon-color-active: #337ab7; +$icon-color-hover: #777777; + +.navbar .navicon { + color: $icon-color-base; +} + +.navbar .navicon:hover .text-center { + text-decoration: underline; +} + +.navbar .navicon:active, .navbar .active-icon { + color:$icon-color-active; +} diff --git a/src/sass/main.scss b/src/sass/main.scss index fe6471cd3..8e7319068 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -31,7 +31,8 @@ './components/_badges', './components/_ballotList', './components/_itemActionbar', - './components/_candidate.scss'; + './components/_candidate.scss', + './components/_navigator.scss'; // 6. Page-specific styles @import From e204518fa47c7f825ca86f39e636c922050c831f Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 21 Feb 2016 03:22:10 -0500 Subject: [PATCH 63/92] 21 menu slides out when link clicked --- src/js/components/Header.jsx | 17 +++++++++++------ src/sass/layout/_mediaquery.scss | 2 +- src/sass/main.scss | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index b349afc9a..a1f1e3642 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -39,6 +39,11 @@ export default class Header extends Component { } } + hide () { + this.setState({visible: false}); + document.querySelector(".container-main").style.opacity = 1; + } + render () { var { location, visible } = this.state; var { voter_photo_url: url, signed_in_personal: signedIn } = this.props; @@ -69,7 +74,7 @@ export default class Header extends Component {
    • - + Sign In
    • @@ -80,17 +85,17 @@ export default class Header extends Component {
        {/*
      • Print or Email Ballot
      • */}
      • - + Opinions I'm Following
      • - + My Ballot Location
      • - + Account Settings
      • @@ -105,7 +110,7 @@ export default class Header extends Component {

        • - + About We Vote
        • @@ -120,7 +125,7 @@ export default class Header extends Component { { signedIn ?
        • - + Sign Out
        • : } diff --git a/src/sass/layout/_mediaquery.scss b/src/sass/layout/_mediaquery.scss index 07ee649cd..c606b01b4 100644 --- a/src/sass/layout/_mediaquery.scss +++ b/src/sass/layout/_mediaquery.scss @@ -34,7 +34,7 @@ top: 43px; transform: translate3d(-10px, 0, 0); -webkit-transform: translate3d(-10px, 0, 0); - width: 90%; + width: 220px; z-index: 2; } } diff --git a/src/sass/main.scss b/src/sass/main.scss index 8e7319068..0b53e6eca 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -31,8 +31,8 @@ './components/_badges', './components/_ballotList', './components/_itemActionbar', - './components/_candidate.scss', - './components/_navigator.scss'; + './components/_candidate', + './components/_navigator'; // 6. Page-specific styles @import From 17ecc9243a8f1c1ee971beaba3bb80b888a3fcd6 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 21 Feb 2016 11:18:58 -0500 Subject: [PATCH 64/92] All errors fixed --- .eslintignore | 1 + .eslintrc | 2 +- package.json | 2 +- src/js/components/Ballot/PositionList.jsx | 4 +- .../Guides/GuidesAddOrganizationPage.jsx | 42 ++--- .../GuidesAddOrganizationResultsPage.jsx | 96 +++++------ .../GuidesAddOrganizationSearchPage.jsx | 42 ++--- .../GuidesConfirmOwnershipEmailSentPage.jsx | 59 +++---- .../Guides/GuidesConfirmOwnershipPage.jsx | 72 ++++---- .../GuidesOrganizationAddExistingLinkPage.jsx | 36 ++-- .../GuidesOrganizationBallotAddItemsPage.jsx | 51 +++--- .../GuidesOrganizationBallotResultsPage.jsx | 34 ++-- .../GuidesOrganizationBallotSearchPage.jsx | 76 ++++----- .../GuidesOrganizationChooseElectionPage.jsx | 28 ++-- .../Guides/GuidesOrganizationDisplayPage.jsx | 154 +++++++++--------- .../Guides/GuidesOrganizationEditPage.jsx | 47 +++--- .../GuidesOrganizationEmailVerifyPage.jsx | 32 ++-- .../GuidesOrganizationPersonalEmailPage.jsx | 42 ++--- .../Guides/GuidesOwnershipConfirmedPage.jsx | 44 ++--- src/js/components/MoreMenu.jsx | 15 +- .../components/OrganizationsToFollowList.jsx | 38 ++--- src/js/routes/Activity.jsx | 12 +- src/js/routes/AddFriends.jsx | 33 ++-- src/js/routes/Ballot/Candidate.jsx | 26 +-- src/js/routes/Connect.jsx | 51 +++--- src/js/routes/Guide/PositionList.jsx | 103 ++++++------ src/js/routes/Home.jsx | 78 +++++---- src/js/routes/Intro/Intro.jsx | 97 +++++------ src/js/routes/Intro/IntroContests.jsx | 8 +- src/js/routes/Intro/IntroOpinions.jsx | 2 +- src/js/routes/More.jsx | 53 +++--- src/js/routes/More/About.jsx | 2 - src/js/routes/More/EmailBallot.jsx | 62 +++---- src/js/routes/More/OpinionsFollowed.jsx | 8 +- src/js/routes/More/Privacy.jsx | 16 +- src/js/routes/More/SignIn.jsx | 2 - src/js/routes/Opinions.jsx | 11 +- src/js/routes/Requests.jsx | 4 +- src/js/utils/connectToStore.jsx | 2 +- 39 files changed, 736 insertions(+), 751 deletions(-) diff --git a/.eslintignore b/.eslintignore index 7dc7fbf62..2952f5022 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ src/js/vendor/*.js +src/js/mock-data/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index d0aaa2b13..f394cb4fc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -101,7 +101,7 @@ "no-label-var": [2], "no-labels": [2], "no-lone-blocks": [2], - "no-lonely-if": [2], + "no-lonely-if": [1], "no-loop-func": [1], "no-mixed-requires": [0, false], "no-mixed-spaces-and-tabs": [2, false], diff --git a/package.json b/package.json index 3126fe8e6..44e4e6c9d 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "repository": "https://github.com/wevote/webapp.git", "scripts": { "lint": "eslint src", - "test": "eslint src", + "test": "eslint --format stylish --ext .jsx --ext .js src/js", "start": "gulp", "deps": "npm run deps:missing && npm run deps:extra", "deps:missing": "dependency-check package.json", diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index d4e3a56c7..b281469e5 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -9,7 +9,9 @@ export default class PositionList extends Component { constructor (props) { super(props); - this.state = { position_list: [] }; + this.state = { + position_list: [] + }; } componentDidMount (){ diff --git a/src/js/components/Guides/GuidesAddOrganizationPage.jsx b/src/js/components/Guides/GuidesAddOrganizationPage.jsx index e5e8e756e..d433e96cf 100644 --- a/src/js/components/Guides/GuidesAddOrganizationPage.jsx +++ b/src/js/components/Guides/GuidesAddOrganizationPage.jsx @@ -15,31 +15,31 @@ export default class GuidesAddOrganizationPage extends React.Component { } render() { - return ( -
          - -
          -

          Enter More Information

          - -
          - + return ( +
          + +
          +

          Enter More Information

          + + + - + - + - + - - -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + + +
          + '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesAddOrganizationResultsPage.jsx b/src/js/components/Guides/GuidesAddOrganizationResultsPage.jsx index 0d4e6366f..570cdbd98 100644 --- a/src/js/components/Guides/GuidesAddOrganizationResultsPage.jsx +++ b/src/js/components/Guides/GuidesAddOrganizationResultsPage.jsx @@ -15,55 +15,55 @@ export default class GuidesAddOrganizationResultsPage extends React.Component { } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Existing Organizations Found

          - - - We found these organizations. Is one of them the organization you are adding? If not, click the 'Create New Voter Guide' button. - + var floatRight = { + float: 'right' + }; + return ( +
          + +
          +

          Existing Organizations Found

          + + + We found these organizations. Is one of them the organization you are adding? If not, click the 'Create New Voter Guide' button. + -
            -
          • - - - - - - -  Organization Name
            {/* TODO icon-org-placeholder */} - - @OrgName1
            - http://www.SomeOrg.org -
            - -
          • -
          • - - - - - - -  Another Organization
            {/* TODO icon-org-placeholder */} - - @OrgName2
            - http://www.SomeOrg.org -
            - -
          • -
          -
          -
          -
          -
          - -
          +
            +
          • + + + + + + +  Organization Name
            {/* TODO icon-org-placeholder */} + + @OrgName1
            + http://www.SomeOrg.org +
            + +
          • +
          • + + + + + + +  Another Organization
            {/* TODO icon-org-placeholder */} + + @OrgName2
            + http://www.SomeOrg.org +
            + +
          • +
          +
          +
          +
          +
          + +
          ); } } diff --git a/src/js/components/Guides/GuidesAddOrganizationSearchPage.jsx b/src/js/components/Guides/GuidesAddOrganizationSearchPage.jsx index 00574ba69..27ef790ff 100644 --- a/src/js/components/Guides/GuidesAddOrganizationSearchPage.jsx +++ b/src/js/components/Guides/GuidesAddOrganizationSearchPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,28 +15,28 @@ export default class GuidesAddOrganizationSearchPage extends React.Component { } render() { - return ( -
          - -
          -

          Enter Organization Information

          - -
          - + return ( +
          + +
          +

          Enter Organization Information

          + + + - + - -
          -
          -
          - -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + +
          +
          +
          + +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesConfirmOwnershipEmailSentPage.jsx b/src/js/components/Guides/GuidesConfirmOwnershipEmailSentPage.jsx index 0042d9989..f8d24411c 100644 --- a/src/js/components/Guides/GuidesConfirmOwnershipEmailSentPage.jsx +++ b/src/js/components/Guides/GuidesConfirmOwnershipEmailSentPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,32 +15,33 @@ export default class GuidesConfirmOwnershipEmailSentPage extends React.Component } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Verification Email Sent

          - + var floatRight = { + float: "right" + }; -
          - Please check your email for the verification email, so you can click the link to confirm that you have - access to the email address 'email@orgemail.org'. Or request another verification email. - - - - - - - -
          -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          - ); - } -} + return ( +
          + +
          +

          Verification Email Sent

          + + +
          + Please check your email for the verification email, so you can click the link to confirm that you have + access to the email address "email@orgemail.org". Or request another verification email. + + + + + + + +
          +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          + ); + } + } diff --git a/src/js/components/Guides/GuidesConfirmOwnershipPage.jsx b/src/js/components/Guides/GuidesConfirmOwnershipPage.jsx index 3b2b7e9b7..c80180d71 100644 --- a/src/js/components/Guides/GuidesConfirmOwnershipPage.jsx +++ b/src/js/components/Guides/GuidesConfirmOwnershipPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; import { Alert, Button, ButtonToolbar, Input, ProgressBar } from "react-bootstrap"; @@ -14,43 +14,43 @@ export default class GuidesConfirmOwnershipPage extends React.Component { } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Confirm You Speak for this Organization

          - + var floatRight = { + float: "right" + }; + return ( +
          + +
          +

          Confirm You Speak for this Organization

          + -
          -
          Method 1
          - - - - - - Sign in with this organization's Twitter account, @orgHandle. -
          -
          -
          -
          Method 2
          - Verify that you can receive email at this organization's domain, @webaddress.org. - - We never sell email addresses. See privacy policy. - - - - - -
          +
          +
          Method 1
          + + + + + + Sign in with this organization"s Twitter account, @orgHandle. +
          +
          +
          +
          Method 2
          + Verify that you can receive email at this organization"s domain, @webaddress.org. + + We never sell email addresses. See privacy policy. + + + + + +
          -
          -
          -
          -
          +
          +
          +
          +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationAddExistingLinkPage.jsx b/src/js/components/Guides/GuidesOrganizationAddExistingLinkPage.jsx index 3132fa566..67bf9ad76 100644 --- a/src/js/components/Guides/GuidesOrganizationAddExistingLinkPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationAddExistingLinkPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,23 +15,23 @@ export default class GuidesOrganizationAddExistingLinkPage extends React.Compone } render() { - return ( -
          - -
          -

          Existing Voter Guide

          - -
          -

          Does your organization already publish a voter guide on the web?

          -
          -
          -
          -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + return ( +
          + +
          +

          Existing Voter Guide

          + +
          +

          Does your organization already publish a voter guide on the web?

          +
          +
          +
          +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationBallotAddItemsPage.jsx b/src/js/components/Guides/GuidesOrganizationBallotAddItemsPage.jsx index d34096fda..8305a7115 100644 --- a/src/js/components/Guides/GuidesOrganizationBallotAddItemsPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationBallotAddItemsPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,32 +15,31 @@ export default class GuidesOrganizationBallotAddItemsPage extends React.Componen } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          - - + var floatRight = { + float: "right" + }; + return ( +
          + +
          + -

          Add More Ballot Items for your Guide

          - -

          Search for more ballot items to include. -

          - - - - - - -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          +

          Add More Ballot Items for your Guide

          + +

          Search for more ballot items to include. +

          + + + + + + +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationBallotResultsPage.jsx b/src/js/components/Guides/GuidesOrganizationBallotResultsPage.jsx index 9743c5509..9993dd44c 100644 --- a/src/js/components/Guides/GuidesOrganizationBallotResultsPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationBallotResultsPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,22 +15,22 @@ export default class GuidesOrganizationBallotResultsPage extends React.Component } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Select Ballot Items for your Guide

          - -

          Choose all of the items you would like to add to your voter guide. -

          -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + var floatRight = { + float: "right" + }; + return ( +
          + +
          +

          Select Ballot Items for your Guide

          + +

          Choose all of the items you would like to add to your voter guide. +

          +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationBallotSearchPage.jsx b/src/js/components/Guides/GuidesOrganizationBallotSearchPage.jsx index 74b1241ae..f6fab340b 100644 --- a/src/js/components/Guides/GuidesOrganizationBallotSearchPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationBallotSearchPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,43 +15,43 @@ export default class GuidesOrganizationBallotSearchPage extends React.Component } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Find Ballot Items for your Guide

          - -

          Search for a specific ballot item to include in your voter guide. -

          - - - - - - -
          -
          - Or
          -
          -
          -

          Search for all ballot items for a specific location. -

          - - - - - - -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + var floatRight = { + float: "right" + }; + return ( +
          + +
          +

          Find Ballot Items for your Guide

          + +

          Search for a specific ballot item to include in your voter guide. +

          + + + + + + +
          +
          + Or
          +
          +
          +

          Search for all ballot items for a specific location. +

          + + + + + + +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationChooseElectionPage.jsx b/src/js/components/Guides/GuidesOrganizationChooseElectionPage.jsx index 5e1f94db8..6f1794deb 100644 --- a/src/js/components/Guides/GuidesOrganizationChooseElectionPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationChooseElectionPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import ElectionsListNavigation from "components/base/ElectionsListNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; @@ -16,19 +16,19 @@ export default class GuidesOrganizationChooseElectionPage extends React.Componen } render() { - return ( -
          - -
          -

          Choose Election

          - -

          Which election are you creating a voter guide for?

          -
          - - -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + return ( +
          + +
          +

          Choose Election

          + +

          Which election are you creating a voter guide for?

          +
          + + +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationDisplayPage.jsx b/src/js/components/Guides/GuidesOrganizationDisplayPage.jsx index 4878a7e5f..ba0ab97c1 100644 --- a/src/js/components/Guides/GuidesOrganizationDisplayPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationDisplayPage.jsx @@ -1,5 +1,5 @@ import AskOrShareAction from "components/base/AskOrShareAction"; -import axios from 'axios'; +import axios from "axios"; import CopyLinkNavigation from "components/navigation/CopyLinkNavigation"; import InfoIconAction from "components/base/InfoIconAction"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; @@ -18,86 +18,86 @@ export default class GuidesOrganizationDisplayPagePage extends React.Component { } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -
            -
          • -

            - -   - Organization Name Voter Guide
            {/* TODO icon-org-placeholder */} -

            - @OrgName1   See Website
            - 5 of your friends follow Organization Name
            - 22,452 people follow
            + var floatRight = { + float: "right" + }; + return ( +
            + +
            +
              +
            • +

              + +   + Organization Name Voter Guide
              {/* TODO icon-org-placeholder */} +

              + @OrgName1   See Website
              + 5 of your friends follow Organization Name
              + 22,452 people follow
              - 2016 General Election, November 2nd - -
              - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. - Phasellus rhoncus hendrerit ultricies. Fusce hendrerit vel elit et euismod. Etiam bibendum ultricies - viverra. Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
              - -
              -
            • -
            -
              -
            • - - {/* Implement later */} - {/* TODO icon-person-placeholder */} -  supports Fictional Candidate - -
              + 2016 General Election, November 2nd + +
              + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. + Phasellus rhoncus hendrerit ultricies. Fusce hendrerit vel elit et euismod. Etiam bibendum ultricies + viverra. Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
              + +
              +
            • +
            +
              +
            • + + + {/* TODO icon-person-placeholder */} +  supports Fictional Candidate + +
              - - Running for US House - District 12 - -
              - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) -
              -
            • -
            -
              -
            • - - {/* Implement later */} - {/* TODO icon-person-placeholder */} -  supports Politician Name - -
              + + Running for US House - District 12 + +
              + Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) +
              +
            • +
            +
              +
            • + + + {/* TODO icon-person-placeholder */} +  supports Politician Name + +
              - - Running for Governor - -
              -
            • -
            -
              -
            • - - {/* Implement later */} - {/* TODO icon-person-placeholder */} -  opposes Another Candidate - -
              + + Running for Governor + +
              +
            • +
            +
              +
            • + + + {/* TODO icon-person-placeholder */} +  opposes Another Candidate + +
              - - Running for Judge - -
              -
            • -
            -
            - -
            + + Running for Judge + +
            +
          • +
          +
          + +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationEditPage.jsx b/src/js/components/Guides/GuidesOrganizationEditPage.jsx index e8354d9b8..84d4307db 100644 --- a/src/js/components/Guides/GuidesOrganizationEditPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationEditPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; import { Button, ButtonToolbar, Input, Navbar } from "react-bootstrap"; @@ -14,30 +14,29 @@ export default class GuidesOrganizationEditPagePage extends React.Component { } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          - - + var floatRight = { + float: "right" + }; + return ( +
          + +
          + -

          Edit Guide

          -

          Search for more ballot items to include. -

          - - - - - - -
          -
          -
          -
          +

          Edit Guide

          +

          Search for more ballot items to include. +

          + + + + + + +
          +
          +
          +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationEmailVerifyPage.jsx b/src/js/components/Guides/GuidesOrganizationEmailVerifyPage.jsx index ba15d812b..ced58a42f 100644 --- a/src/js/components/Guides/GuidesOrganizationEmailVerifyPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationEmailVerifyPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,21 +15,21 @@ export default class GuidesOrganizationEmailVerifyPage extends React.Component { } render() { - return ( -
          - -
          -

          Verification Email Sent

          - -
          -

          Thank you, an email has been sent to 'email@email.com' with the subject 'Please verify your email address'.

          -
          -
          -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + return ( +
          + +
          +

          Verification Email Sent

          + +
          +

          Thank you, an email has been sent to "email@email.com" with the subject "Please verify your email address".

          +
          +
          +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOrganizationPersonalEmailPage.jsx b/src/js/components/Guides/GuidesOrganizationPersonalEmailPage.jsx index afdb62593..0ccfb9a66 100644 --- a/src/js/components/Guides/GuidesOrganizationPersonalEmailPage.jsx +++ b/src/js/components/Guides/GuidesOrganizationPersonalEmailPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import BottomContinueNavigation from "components/navigation/BottomContinueNavigation"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; @@ -15,26 +15,26 @@ export default class GuidesOrganizationPersonalEmailPage extends React.Component } render() { - return ( -
          - -
          -

          Verify Email

          - -

          To create a public voter guide, we need to verify your email address. - This provides protection from spammers. -

          -
          - - This email address will not be shared with the public, - and is protected by our privacy policy.
          -
          -
          -
          -
          - '} link_route_cancel={'guides_voter'} cancel_text={"cancel"} /> -
          + return ( +
          + +
          +

          Verify Email

          + +

          To create a public voter guide, we need to verify your email address. + This provides protection from spammers. +

          +
          + + This email address will not be shared with the public, + and is protected by our privacy policy.
          +
          +
          +
          +
          + "} link_route_cancel={"guides_voter"} cancel_text={"cancel"} /> +
          ); } } diff --git a/src/js/components/Guides/GuidesOwnershipConfirmedPage.jsx b/src/js/components/Guides/GuidesOwnershipConfirmedPage.jsx index 27219aba7..fd05309db 100644 --- a/src/js/components/Guides/GuidesOwnershipConfirmedPage.jsx +++ b/src/js/components/Guides/GuidesOwnershipConfirmedPage.jsx @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios from "axios"; import HeaderBackNavigation from "components/navigation/HeaderBackNavigation"; import React from "react"; import { Alert, Button, ButtonToolbar, Input, ProgressBar } from "react-bootstrap"; @@ -14,28 +14,28 @@ export default class GuidesOwnershipConfirmedPage extends React.Component { } render() { - var floatRight = { - float: 'right' - }; - return ( -
          - -
          -

          Your Authority is Confirmed

          - + var floatRight = { + float: "right" + }; + return ( +
          + +
          +

          Your Authority is Confirmed

          + -
          - - - - - - Go to the interface where you can edit this voter guide. -
          -
          -
          -
          -
          +
          + + + + + + Go to the interface where you can edit this voter guide. +
          +
          +
          +
          +
          ); } } diff --git a/src/js/components/MoreMenu.jsx b/src/js/components/MoreMenu.jsx index e9133a710..7ad8b4683 100644 --- a/src/js/components/MoreMenu.jsx +++ b/src/js/components/MoreMenu.jsx @@ -6,10 +6,10 @@ import VoterStore from "../stores/VoterStore"; export default class MoreMenu extends Component { static propTypes = { - email: PropTypes.string, - first_name: PropTypes.string, - voter_photo_url: PropTypes.string, - signed_in_personal: PropTypes.bool + email: PropTypes.string, + first_name: PropTypes.string, + voter_photo_url: PropTypes.string, + signed_in_personal: PropTypes.bool }; constructor(props) { @@ -41,7 +41,7 @@ export default class MoreMenu extends Component { }
            {/*
          • Print or Email Ballot
          • */} -
          • Opinions I'm Following
          • +
          • Opinions I"m Following
          • My Ballot Location
          • Account Settings
          @@ -56,8 +56,9 @@ export default class MoreMenu extends Component {
          • About We Vote
          • {/*
          • Terms & Policies
          • */} -
          • Admin
          • +
          • + Admin
          • {/* {this.props.signed_in_personal ?
          • Sign Out
          • diff --git a/src/js/components/OrganizationsToFollowList.jsx b/src/js/components/OrganizationsToFollowList.jsx index 732c1cf84..e87ad9721 100644 --- a/src/js/components/OrganizationsToFollowList.jsx +++ b/src/js/components/OrganizationsToFollowList.jsx @@ -12,17 +12,17 @@ export default class OrganizationsToFollowList extends Component {
            - + {/* TODO icon-org-placeholder */} -
            - -
            -
            - - Organization Name - -
            +
            + +
            +
            + + Organization Name + +
            @OrgName1 (read more) @@ -33,20 +33,20 @@ export default class OrganizationsToFollowList extends Component { {/* TODO icon-org-placeholder */} -
            - -
            -
            - - Another Organization Name - -
            +
            + +
            +
            + + Another Organization Name + +
            @OrgName2 (read more) -
            +
    - ); + ); } } diff --git a/src/js/routes/Activity.jsx b/src/js/routes/Activity.jsx index 7c23502fa..14e17e039 100644 --- a/src/js/routes/Activity.jsx +++ b/src/js/routes/Activity.jsx @@ -1,11 +1,9 @@ import React, { PropTypes, Component } from 'react'; -{/* VISUAL DESIGN HERE: TBD */} - export default class Activity extends Component { - static propTypes = { - children: PropTypes.object - }; + static propTypes = { + children: PropTypes.object + }; constructor(props) { super(props); @@ -16,9 +14,9 @@ export default class Activity extends Component { } render() { - return ( + return (
    -
    +

    Activity Feed

    Coming Soon

    See the latest endorsements and news.

    diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx index 95414d0ed..655364223 100644 --- a/src/js/routes/AddFriends.jsx +++ b/src/js/routes/AddFriends.jsx @@ -3,7 +3,6 @@ import { Link } from 'react-router'; import { Input } from 'react-bootstrap'; import BottomContinueNavigation from '../components/Navigation/BottomContinueNavigation'; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479679 */} export default class AddFriends extends Component { constructor(props) { @@ -15,29 +14,29 @@ export default class AddFriends extends Component { } render() { - return ( -
    -
    + return ( +
    +

    Add Friends

    Coming Soon

    You will be able to ask your friends for their opinions on how to vote.

    {/* Still to be implemented -

    Add Friends

    -
    -
    -
    - - These friends will see what you support, oppose, and which opinions you follow. - We never sell email addresses.
    -
    - */} -
    +

    Add Friends

    +
    +
    +
    + + These friends will see what you support, oppose, and which opinions you follow. + We never sell email addresses.
    +
    + */} +
    {/* Still to be implemented */} -
    +
    ); } } diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index d17e5477a..5de11219e 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -1,13 +1,13 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component, PropTypes } from "react"; import { Button, ButtonToolbar, DropdownButton, Input, MenuItem, Navbar } from "react-bootstrap"; -import { Link } from 'react-router'; +import { Link } from "react-router"; -import BallotStore from '../../stores/BallotStore'; -import CandidateDetail from '../../components/Ballot/CandidateDetail'; -import PositionList from '../../components/Ballot/PositionList'; -import ItemActionbar from '../../components/ItemActionbar'; -import ItemActionBar2 from '../../components/ItemActionBar2'; -import StarAction from '../../components/StarAction'; +import BallotStore from "../../stores/BallotStore"; +import CandidateDetail from "../../components/Ballot/CandidateDetail"; +import PositionList from "../../components/Ballot/PositionList"; +import ItemActionbar from "../../components/ItemActionbar"; +import ItemActionBar2 from "../../components/ItemActionBar2"; +import StarAction from "../../components/StarAction"; export default class Candidate extends Component { static propTypes = { @@ -49,7 +49,7 @@ export default class Candidate extends Component { {/*
    - + < Back to My Ballot
    @@ -64,23 +64,23 @@ export default class Candidate extends Component {
    */} - {candidate.hasOwnProperty('is_starred') ? + {candidate.hasOwnProperty("is_starred") ? :
    } -
    +
    + style={candidate.candidate_photo_url ? {} : {height:"95px"}}> { candidate.candidate_photo_url ? candidate-photo : diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 2282fccc9..261518cca 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -1,18 +1,15 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router'; +import React, { Component } from "react"; +import { Link } from "react-router"; +import { Button } from "react-bootstrap"; +import OrganizationsToFollowList from "../components/OrganizationsToFollowList"; -import { Button } from 'react-bootstrap'; - -import OrganizationsToFollowList from '../components/OrganizationsToFollowList'; - -{/* VISUAL DESIGN HERE: https://invis.io/E45246B2C */} export default class Connect extends Component { - static propTypes = { + static propTypes = { - }; + }; - constructor(props) { + constructor(props) { super(props); } @@ -21,33 +18,33 @@ export default class Connect extends Component { } render() { - var floatRight = { - float: 'right' - }; - return ( + var floatRight = { + float: "right" + }; + return (

    Add Friends

    - - - -

    Friends can see what you support and oppose. We never sell emails.
    -

    + + + +

    Friends can see what you support and oppose. We never sell emails.
    +

    Follow More Opinions

    - - - -

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.
    -

    + + + +

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.
    +

    {/* - + -
    - */} +
    + */}
    ); diff --git a/src/js/routes/Guide/PositionList.jsx b/src/js/routes/Guide/PositionList.jsx index 67dfb90c2..c44f19287 100755 --- a/src/js/routes/Guide/PositionList.jsx +++ b/src/js/routes/Guide/PositionList.jsx @@ -1,15 +1,14 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component, PropTypes } from "react"; import { Button, ButtonToolbar, DropdownButton, Input, MenuItem, Navbar } from "react-bootstrap"; -import { Link } from 'react-router'; +import { Link } from "react-router"; -import BallotActions from '../../actions/BallotActions'; -import BallotStore from '../../stores/BallotStore'; -import CandidateDetail from '../../components/Ballot/CandidateDetail'; -import ItemActionbar from '../../components/ItemActionbar'; -import ItemActionBar2 from '../../components/ItemActionBar2'; -import StarAction from '../../components/StarAction'; +import BallotActions from "../../actions/BallotActions"; +import BallotStore from "../../stores/BallotStore"; +import CandidateDetail from "../../components/Ballot/CandidateDetail"; +import ItemActionbar from "../../components/ItemActionbar"; +import ItemActionBar2 from "../../components/ItemActionBar2"; +import StarAction from "../../components/StarAction"; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/94226088 */} export default class GuidePositionList extends Component { static propTypes = { @@ -24,54 +23,50 @@ export default class GuidePositionList extends Component { render() { var floatRight = { - float: 'right' + float: "right" }; - // var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); - return ( -
    -
    -
      -
    • -

      - -   - Organization Name Voter Guide
      {/* TODO icon-org-placeholder */} -

      - @OrgName1   See Website
      - 5 of your friends follow Organization Name
      - 22,452 people follow
      - - 2016 General Election, November 2nd -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. - Phasellus rhoncus hendrerit ultricies. Fusce hendrerit vel elit et euismod. Etiam bibendum ultricies - viverra. Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
      - {/**/} -
      -
    • -
    -
      -
    • - - {/* Implement later */} - {/* TODO icon-person-placeholder */} -  supports Fictional Candidate -
      +
      +
      +
        +
      • +

        + +   + Organization Name Voter Guide
        {/* TODO icon-org-placeholder */} +

        + @OrgName1   See Website
        + 5 of your friends follow Organization Name
        + 22,452 people follow
        - - Running for US House - District 12 -
        - Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. - Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) -
        -
      • -
      -
      - {/**/} -
      - ); + 2016 General Election, November 2nd +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur posuere vulputate massa ut efficitur. + Phasellus rhoncus hendrerit ultricies. Fusce hendrerit vel elit et euismod. Etiam bibendum ultricies + viverra. Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more)
      + {/**/} +
      +
    • +
    +
      +
    • + + +  supports Fictional Candidate +
      + + Running for US House - District 12 +
      + Integer ut bibendum ex. Suspendisse eleifend mi accumsan, euismod enim at, malesuada nibh. + Duis a eros fringilla, dictum leo vitae, vulputate mi. Nunc vitae neque nec erat fermentum... (more) +
      +
    • +
    +
    + {/**/} +
    + ); } } diff --git a/src/js/routes/Home.jsx b/src/js/routes/Home.jsx index 3c0d0cce7..3c0195e59 100644 --- a/src/js/routes/Home.jsx +++ b/src/js/routes/Home.jsx @@ -1,21 +1,21 @@ -import React, { Component } from 'react'; -import { Button, Input } from 'react-bootstrap'; -import { Link } from 'react-router'; +import React, { Component } from "react"; +import { Button, Input } from "react-bootstrap"; +import { Link } from "react-router"; -import * as cookies from '../utils/cookies'; +import * as cookies from "../utils/cookies"; export default class Home extends Component { constructor(props) { super(props); this.state = { - device_verified: cookies.getItem('voter_device_id') ? true : false, + device_verified: cookies.getItem("voter_device_id") ? true : false, voter_count: null, organization_count: null }; if (this.state.device_verified) - location.href='ballot'; + location.href="ballot"; } static getProps() { @@ -35,10 +35,10 @@ export default class Home extends Component { deviceIdGenerate(function (err, res) { if (res.ok) { console.log(res.body); - cookies.setItem('voter_device_id', res.body.voter_device_id); + cookies.setItem("voter_device_id", res.body.voter_device_id); } else console.log(err); - }.bind(this)); + }); } /** @@ -51,13 +51,13 @@ export default class Home extends Component { getVoterCount() { this.setState({ - voter_count: '1001am' + voter_count: "1001am" }); } getOrganizationCount() { this.setState({ - organization_count: 'a lot' + organization_count: "a lot" }); } @@ -69,37 +69,33 @@ export default class Home extends Component {

    Loading ... One Moment

    - ) : ( -
    -

    We Vote Social Voter Guide

    -
      -
    • Research ballot items
    • -
    • Learn from friends
    • -
    • Take to the polls
    • -
    + ) : + ( +
    +

    We Vote Social Voter Guide

    +
      +
    • Research ballot items
    • +
    • Learn from friends
    • +
    • Take to the polls
    • +
    + +
      +
    •  Neutral and private
    • +
    •  {this.state.voter_count} voters
    • +
    •  {this.state.organization_count} not-for-profit organizations
    • +
    •  and you.
    • +
    +
    + This is our best guess - feel free to change + + + + +
    +
    +
    + ); -
      -
    •  Neutral and private
    • -
    •  {this.state.voter_count} voters
    • - {/* TODO When we upgrade to react@0.14.0 we can use react-intl@2.0.0-beta-1 -
    •   - {' '} -
    • */} -
    •  {this.state.organization_count} not-for-profit organizations
    • -
    •  and you.
    • -
    -
    - This is our best guess - feel free to change - - - - -
    -
    -
    ); return view; - } + } } diff --git a/src/js/routes/Intro/Intro.jsx b/src/js/routes/Intro/Intro.jsx index 232b3a828..2cfecfb9f 100644 --- a/src/js/routes/Intro/Intro.jsx +++ b/src/js/routes/Intro/Intro.jsx @@ -1,11 +1,11 @@ -'use strict'; -import React, { Component, PropTypes } from 'react'; +"use strict"; +import React, { Component, PropTypes } from "react"; -import { Button, Input } from 'react-bootstrap'; -import { Link } from 'react-router'; +import { Button, Input } from "react-bootstrap"; +import { Link } from "react-router"; -const request = require('superagent'); -const web_app_config = require('../../config'); +const request = require("superagent"); +const web_app_config = require("../../config"); export default class Intro extends Component { static propTypes = { @@ -64,60 +64,49 @@ export default class Intro extends Component { { this.props.children ||

    We Vote Social Voter Guide

    -
      -
    • Research ballot items
    • -
    • Learn from friends
    • -
    • Take to the polls
    • -
    +
      +
    • Research ballot items
    • +
    • Learn from friends
    • +
    • Take to the polls
    • +
    -
      -
    • - -  Neutral and private -
    • -
    • - -   {this.state.voterCount} voters -
    • - {/* TODO When we upgrade to react@0.14.0 we can use react-intl@2.0.0-beta-1 -
    •   - {' '} -
    • */} -
    • - -   {this.state.orgCount} not-for-profit organizations -
    • -
    • - -  and you. -
    • -
    - -
    - - This is our best guess - feel free to change - - +
  • +  Neutral and private +
  • +
  • +   {this.state.voterCount} voters +
  • +
  • +   {this.state.orgCount} not-for-profit organizations +
  • +
  • +  and you. +
  • + + +
    + + This is our best guess - feel free to change + + - - - -
    -
    -
    - } -
    + + +
    +
    +
    + } +
    ); } } diff --git a/src/js/routes/Intro/IntroContests.jsx b/src/js/routes/Intro/IntroContests.jsx index a5f5ba5d4..c310d384d 100644 --- a/src/js/routes/Intro/IntroContests.jsx +++ b/src/js/routes/Intro/IntroContests.jsx @@ -16,14 +16,14 @@ export default class IntroBallotContests extends Component { render() { var float = { right: { - float: 'right' + float: 'right' }, left: { - float: 'left' + float: 'left' } }; - return ( + return (

    We have found your ballot for this location:

    @@ -131,7 +131,7 @@ export default class IntroBallotContests extends Component {
    diff --git a/src/js/routes/Intro/IntroOpinions.jsx b/src/js/routes/Intro/IntroOpinions.jsx index bbeafa8be..bcd7e6c6e 100644 --- a/src/js/routes/Intro/IntroOpinions.jsx +++ b/src/js/routes/Intro/IntroOpinions.jsx @@ -23,7 +23,7 @@ export default class IntroOpinionsPage extends Component { } }; - return ( + return (

    Here's the idea - Learn from Community

    diff --git a/src/js/routes/More.jsx b/src/js/routes/More.jsx index 5a5f1a957..f74e80abb 100644 --- a/src/js/routes/More.jsx +++ b/src/js/routes/More.jsx @@ -19,27 +19,40 @@ export default class More extends Component { /* DALE 2015-01-13 I believe we will want to deprecate this file in favor of /src/components/MoreMenu.jsx */ render() { - return ( + return (
    -
    -

    -
      -
    • Print, Save or Email Ballot
    • -
    • Opinions I Follow
    • -
    -

    -
      -
    • My Ballot Location
    • -
    -

    -
      -
    • About We Vote
    • -
    • Terms and Policies
    • -
    • Admin
    • -
    - -
    +
    +

    +
      +
    • + Print, Save or Email Ballot +
    • +
    • + Opinions I Follow +
    • +
    +

    +
      +
    • + My Ballot Location +
    • +
    +

    +
      +
    • + About We Vote +
    • +
    • + Terms and Policies +
    • +
    • + Admin + +
    • +
    + +
    ); } diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index d1b04eca3..bfd007029 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -1,8 +1,6 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/90192590 */} - export default class About extends Component { constructor(props) { super(props); diff --git a/src/js/routes/More/EmailBallot.jsx b/src/js/routes/More/EmailBallot.jsx index d2bf614c0..581b87266 100755 --- a/src/js/routes/More/EmailBallot.jsx +++ b/src/js/routes/More/EmailBallot.jsx @@ -3,8 +3,6 @@ import { Button, ButtonToolbar, Input } from "react-bootstrap"; import { Link } from "react-router"; import Main from '../../components/Facebook/Main'; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479656 */} - export default class EmailBallot extends Component { constructor(props) { super(props); @@ -15,33 +13,39 @@ export default class EmailBallot extends Component { } render() { - return ( -
    -
    -

    Print, Save or Email Ballot

    -
    -
    - - Email your ballot to yourself so you can print it, or come back - to it later. We will never sell your email address. - See privacy policy. -
    - - -
    -
    -
    - OR
    -
    - -
    - -
    -
    -
    -
    -
    + return ( +
    +
    +

    Print, Save or Email Ballot

    +
    +
    + + Email your ballot to yourself so you can print it, or come back + to it later. We will never sell your email address. + See privacy policy. +
    + + +
    +
    +
    + OR +
    +
    + +
    + + +
    +
    +
    +
    +
    ); } } diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index 6364109d8..b04b0c6c4 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -1,10 +1,8 @@ import React, {Component, PropTypes } from "react"; import HeaderBackNavigation from "../../components/Navigation/HeaderBackNavigation"; -import VoterGuideStore from '../../stores/VoterGuideStore'; -import VoterGuideItem from '../../components/VoterGuide/VoterGuideItem'; - -{/* VISUAL DESIGN HERE: https://invis.io/8F53FDX9G */} +import VoterGuideStore from "../../stores/VoterGuideStore"; +import VoterGuideItem from "../../components/VoterGuide/VoterGuideItem"; export default class OpinionsFollowed extends Component { static propTypes = { @@ -24,7 +22,7 @@ export default class OpinionsFollowed extends Component { return (
    -

    Opinions I'm Following

    +

    Opinions I"m Following

    {/*
    diff --git a/src/js/routes/More/Privacy.jsx b/src/js/routes/More/Privacy.jsx index c408698a0..6cf179d51 100755 --- a/src/js/routes/More/Privacy.jsx +++ b/src/js/routes/More/Privacy.jsx @@ -2,8 +2,6 @@ import React from "react"; import { Button, ButtonToolbar } from "react-bootstrap"; import { Link } from "react-router"; -{/* VISUAL DESIGN HERE: */} - export default class Privacy extends React.Component { constructor(props) { super(props); @@ -14,13 +12,13 @@ export default class Privacy extends React.Component { } render() { - return ( -
    -
    -

    Terms and Policies

    - Coming soon. -
    -
    + return ( +
    +
    +

    Terms and Policies

    + Coming soon. +
    +
    ); } } diff --git a/src/js/routes/More/SignIn.jsx b/src/js/routes/More/SignIn.jsx index e5397b023..01425cf48 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -12,8 +12,6 @@ import FacebookSignIn from "../../components/Facebook/FacebookSignIn"; import Main from "../../components/Facebook/Main"; import VoterStore from "../../stores/VoterStore"; -{/* VISUAL DESIGN: tbd */} - export default class SignIn extends Component { static propTypes = { children: PropTypes.object diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 2db5513e9..5f929ae2c 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -1,10 +1,9 @@ -import React, {Component, PropTypes } from 'react'; -import HeaderBackNavigation from '../Components/Navigation/HeaderBackNavigation'; +import React, {Component, PropTypes } from "react"; +import HeaderBackNavigation from "../Components/Navigation/HeaderBackNavigation"; -import VoterGuideStore from '../stores/VoterGuideStore'; -import VoterGuideItem from '../components/VoterGuide/VoterGuideItem'; +import VoterGuideStore from "../stores/VoterGuideStore"; +import VoterGuideItem from "../components/VoterGuide/VoterGuideItem"; -{/* VISUAL DESIGN HERE: https://invis.io/TR4A1NYAQ */} export default class Opinions extends Component { static propTypes = { @@ -30,7 +29,7 @@ export default class Opinions extends Component { placeholder="Search by name or twitter handle." /> */}

    These organizations and public figures have opinions about items on your - ballot. Click the 'Follow' button to pay attention to them.

    + ballot. Click the "Follow" button to pay attention to them.

    { diff --git a/src/js/routes/Requests.jsx b/src/js/routes/Requests.jsx index f2dc07fdd..e2a3d6535 100644 --- a/src/js/routes/Requests.jsx +++ b/src/js/routes/Requests.jsx @@ -13,9 +13,9 @@ export default class RequestsPage extends Component { } render() { - return ( + return (
    -
    +

    Friend Requests

    Coming Soon

    Friends will be able to reach out to you so you can collaborate on how to vote.

    diff --git a/src/js/utils/connectToStore.jsx b/src/js/utils/connectToStore.jsx index 424e324f9..7b1d88305 100644 --- a/src/js/utils/connectToStore.jsx +++ b/src/js/utils/connectToStore.jsx @@ -1,4 +1,4 @@ -import { Component } from 'react'; +import React, { Component } from 'react'; import shallowEqual from 'react-pure-render/shallowEqual'; export default function connectToStores(stores, getState) { From 3f4dad3acf7d97aede3936570337647305bab11f Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 21 Feb 2016 14:39:12 -0500 Subject: [PATCH 65/92] Added Uncle Sam holding logo --- README.md | 6 +++--- unclesamewevote.jpg | Bin 0 -> 36845 bytes 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 unclesamewevote.jpg diff --git a/README.md b/README.md index 1e8459de1..a36e4266b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# README for We Vote WebApp - -![WeVoteUS](wevotelogo.png) +# We Vote WebApp [![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) +![WeVoteUS](unclesamewevote.jpg) + This WebApp repository contains a Node/React/Flux Javascript application. Using data from Google Civic API, Vote Smart, MapLight, TheUnitedStates.io and the Voting Information Project, we give voters a social way to interact with ballot data. diff --git a/unclesamewevote.jpg b/unclesamewevote.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85e69e6e9c959a0fe75639b195bb56ea6a8e5518 GIT binary patch literal 36845 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 I@ps_=17+hdCIA2c literal 0 HcmV?d00001 From 4e05d1c7f4f223fb2a4d8efeb396541477b5574a Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 22 Feb 2016 03:06:58 -0500 Subject: [PATCH 66/92] appveyor build script --- appveyor.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..209178257 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,43 @@ +# Thanks for Grunt for template of this file! + +# http://www.appveyor.com/docs/appveyor-yml + +# Fix line endings in Windows. (runs before repo cloning) +init: + - git config --global core.autocrlf input + +# Test against these versions of Node.js. +environment: + matrix: + - nodejs_version: "0.10" + - nodejs_version: "0.12" + - nodejs_version: "5" + - nodejs_version: "4" + +# Allow failing jobs for bleeding-edge Node.js versions. +matrix: + allow_failures: + - nodejs_version: "0.10" + - nodejs_version: "5" + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node 0.STABLE.latest + - ps: Install-Product node $env:nodejs_version + # Output useful info for debugging. + - node --version + - npm --version + - git --version + - svn --version + # Install all dependencies + - npm install + +# Post-install test scripts. +test_script: + - cmd: npm test + +# Don't actually build. +build: off + +# Set build version format here instead of in the admin panel. +version: "{build}" \ No newline at end of file From c832974872446b7bdb4efe75da02fcfc62dcec93 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 00:01:52 -0800 Subject: [PATCH 67/92] Add redirects when ballot is empty or user is not following any voter guides --- src/js/routes/Ballot/Ballot.jsx | 8 +++++++- src/js/routes/More/OpinionsFollowed.jsx | 8 +++++++- src/js/stores/BallotStore.js | 6 ++++++ src/js/stores/VoterGuideStore.js | 13 ++++++++----- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 7726f5dc2..5ea84c5e1 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -16,7 +16,13 @@ export default class Ballot extends Component { } componentDidMount () { - BallotStore.initialize( (ballot_list) => this.setState({ballot_list}) ) + BallotStore.initialize( function(ballot_list){ + if (ballot_list.length === 0){ + this.props.history.push('settings/location'); + } else { + this.setState({ballot_list}); + } + }.bind(this)); } render () { diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index 6364109d8..de4a3e3a6 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -17,7 +17,13 @@ export default class OpinionsFollowed extends Component { } componentDidMount () { - VoterGuideStore.initializeGuidesFollowed( voter_guide_followed_list => this.setState({ voter_guide_followed_list })); + VoterGuideStore.initializeGuidesFollowed( function(voter_guide_followed_list) { + if (voter_guide_followed_list !== undefined && voter_guide_followed_list.length > 0){ + this.setState({ voter_guide_followed_list }); + } else { + this.props.history.push('/opinions'); + } + }.bind(this)); } render() { diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 2bca70316..3b5177835 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -310,6 +310,12 @@ const BallotStore = createStore({ } }); + //check for no results, then return empty array rather than creating promises from queue + if (res.ballot_item_list.length === 0 ){ + callback(getOrderedBallotItems()); + return; + } + // this function polls requests for complete status. new Promise( (resolve) => { var counted = []; diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 06d5d92fd..9e346df29 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -63,10 +63,13 @@ function retrieveVoterGuidesFollowedList () { .withCredentials() .query({ voter_device_id: cookies.getItem("voter_device_id") }) .end( function (err, res) { - if (err || !res.body.success) - reject(err || res.body.status); + if (err || !res.body.success){ + reject(res.body); + // reject(err || res.body.status); console.log("Reached out to retrieveVoterGuidesFollowedList"); - resolve(res.body); + } else { + resolve(res.body); + } }) ); } @@ -224,7 +227,7 @@ const VoterGuideStore = createStore({ .then(addVoterGuidesToFollowToVoterGuideStore) // Uses data retrieved with retrieveVoterGuidesToFollowList .then(retrieveOrganizations) .then(data => callback(getItems())) - .catch(err => console.error(err)); + .catch(err => callback(err)); }, /** @@ -252,7 +255,7 @@ const VoterGuideStore = createStore({ .then(retrieveOrganizationsFollowedList) .then(addOrganizationsFollowedToStore) // Uses data from retrieveOrganizationsFollowedList .then(data => callback(getFollowedItems())) - .catch(err => console.error(err)); + .catch(err => callback(err)); }, /** From 361d05f00621e98e2c258a5c547a59a84d88e557 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 22 Feb 2016 05:08:08 -0500 Subject: [PATCH 68/92] Add unit testing integration --- package.json | 8 +++++--- tests/sampleTest.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/sampleTest.js diff --git a/package.json b/package.json index 44e4e6c9d..26c02997a 100644 --- a/package.json +++ b/package.json @@ -65,13 +65,14 @@ "react-router-bootstrap": "^0.16.0", "react-select": "^0.9.1", "react-tap-event-plugin": "^0.2.1", + "tape": "^4.4.0", "vinyl-source-stream": "^1.1.0", "yargs": "^3.31.0" }, "repository": "https://github.com/wevote/webapp.git", "scripts": { - "lint": "eslint src", - "test": "eslint --format stylish --ext .jsx --ext .js src/js", + "lint": "eslint --format stylish --ext .jsx --ext .js src/js", + "test": "babel-node tests/*.js | faucet", "start": "gulp", "deps": "npm run deps:missing && npm run deps:extra", "deps:missing": "dependency-check package.json", @@ -79,6 +80,7 @@ "build:doc": "doctoc --github --title \"## Contents\" ./" }, "pre-commit": [ - "test" + "test", + "lint" ] } diff --git a/tests/sampleTest.js b/tests/sampleTest.js new file mode 100644 index 000000000..208ce6df8 --- /dev/null +++ b/tests/sampleTest.js @@ -0,0 +1,19 @@ +import test from 'tape'; + +test('A passing test', (assert) => { + + assert.pass('This test will pass'); + + assert.end(); +}); + +test('Assertions with tape.', (assert) => { + + const expected = 'something to test'; + const actual = 'something to test'; + + assert.equal(actual, expected, + 'Given two mismatched values, .equal() should produce a nice bug report'); + + assert.end(); +}); From 2af639533321f1a8d24bc0980c03cb84ffd98901 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 22 Feb 2016 05:44:09 -0500 Subject: [PATCH 69/92] Adding Code of Conduct --- CODE_OF_CONDUCT.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..16f472b29 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,36 @@ +#WeVoteUSA Code of Conduct +--- +##Conduct + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. +* On public communication channels, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact the team immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. + +--- + + +##Moderation + +These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the WeVoteUSA moderation team. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. + +In the WeVoteUSA community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. + +The enforcement policies listed above apply to all official WeVoteUSA venues; including official [Slack channel](http://wevote.slack.com) and GitHub repositories under WeVoteUSA. For other projects adopting the WeVoteUSA Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. + +Adapted from the [Rust CoC](http://www.rust-lang.org/conduct.html), [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](http://contributor-covenant.org/version/1/3/0/) \ No newline at end of file From a3763fce8b54baffa0bd609bad8959e7ddc4590f Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 09:15:41 -0800 Subject: [PATCH 70/92] Add formatting utils and format date on positionitem view --- src/js/components/Ballot/PositionItem.jsx | 5 ++++- src/js/utils/formatter.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/js/utils/formatter.js diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 5d3347f91..5a5b19cf3 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -1,6 +1,7 @@ import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import PositionStore from "../../stores/PositionStore"; +import Formatter from "../../utils/formatter"; export default class PositionItem extends Component { static propTypes = { @@ -29,6 +30,8 @@ export default class PositionItem extends Component { render () { var position = this.state.position; var supportText = position.is_oppose ? "Opposes" : "Supports"; + var dateText = Formatter.formatDate(this.props.last_updated); + return
    {/* One organization"s Position on this Candidate */}
  • @@ -47,7 +50,7 @@ export default class PositionItem extends Component {

    { this.props.speaker_display_name }

    -

    {supportText} { this.props.last_updated }

    +

    {supportText} { dateText }

  • diff --git a/src/js/utils/formatter.js b/src/js/utils/formatter.js new file mode 100644 index 000000000..6fbea6282 --- /dev/null +++ b/src/js/utils/formatter.js @@ -0,0 +1,14 @@ +module.exports = { + formatDate: function(str){ + console.log(str); + var dateText; + var date = new Date(str); + var now = new Date(); + if (date.toDateString() === now.toDateString()){ // Updated sometime today, display time + dateText = "Today at " + date.toLocaleTimeString('en-US'); + } else { + dateText = Date(str).toDateString(); + } + return dateText; + } +} From 5f0f8df9e1b65d0ff08ce43ab6c2e73589011266 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 09:49:44 -0800 Subject: [PATCH 71/92] Use momentJS date formatting --- package.json | 3 ++- src/js/components/Ballot/PositionItem.jsx | 5 +++-- src/js/utils/formatter.js | 14 -------------- 3 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 src/js/utils/formatter.js diff --git a/package.json b/package.json index 44e4e6c9d..1fd9f76df 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "keymirror": "^0.1.1", + "moment": "^2.11.2", "object-assign": "^4.0.1", "react": "^0.14.3", "react-dom": "^0.14.3", @@ -79,6 +80,6 @@ "build:doc": "doctoc --github --title \"## Contents\" ./" }, "pre-commit": [ - "test" + "test" ] } diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 5a5b19cf3..fe77672fb 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -1,7 +1,7 @@ import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import PositionStore from "../../stores/PositionStore"; -import Formatter from "../../utils/formatter"; +const moment = require('moment'); export default class PositionItem extends Component { static propTypes = { @@ -30,7 +30,8 @@ export default class PositionItem extends Component { render () { var position = this.state.position; var supportText = position.is_oppose ? "Opposes" : "Supports"; - var dateText = Formatter.formatDate(this.props.last_updated); + var dateStr = this.props.last_updated; + var dateText = moment(dateStr).startOf('day').fromNow(); return
    {/* One organization"s Position on this Candidate */} diff --git a/src/js/utils/formatter.js b/src/js/utils/formatter.js deleted file mode 100644 index 6fbea6282..000000000 --- a/src/js/utils/formatter.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - formatDate: function(str){ - console.log(str); - var dateText; - var date = new Date(str); - var now = new Date(); - if (date.toDateString() === now.toDateString()){ // Updated sometime today, display time - dateText = "Today at " + date.toLocaleTimeString('en-US'); - } else { - dateText = Date(str).toDateString(); - } - return dateText; - } -} From 2bcd98768f0ff8f25c61415bae41672b22e2f278 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 13:25:41 -0800 Subject: [PATCH 72/92] Fix bug with position list and add candidate name to positionItem view --- src/js/actions/PositionActions.js | 4 ++-- src/js/components/Ballot/PositionItem.jsx | 17 ++++++++++++++--- src/js/components/Ballot/PositionList.jsx | 8 +++++--- src/js/routes/Ballot/Candidate.jsx | 2 +- src/js/stores/PositionStore.js | 4 ++-- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/js/actions/PositionActions.js b/src/js/actions/PositionActions.js index 4f92f8ac0..6c3af4f32 100644 --- a/src/js/actions/PositionActions.js +++ b/src/js/actions/PositionActions.js @@ -4,11 +4,11 @@ var PositionConstants = require("../constants/PositionConstants"); module.exports = { - positionRetrieved: function (we_vote_id, payload) { + positionRetrieved: function (payload) { AppDispatcher.dispatch({ actionType: PositionConstants.POSITION_RETRIEVED, payload: payload, - we_vote_id: we_vote_id + we_vote_id: payload.position_we_vote_id }); }, diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 5d3347f91..b93f92fd0 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -23,12 +23,23 @@ export default class PositionItem extends Component { } _onChange () { - this.setState({ position: PositionStore.getLocalPositionByWeVoteId(this.props.position_we_vote_id) }); + var position = PositionStore.getLocalPositionByWeVoteId(this.props.position_we_vote_id); + this.setState({ position: position }); + console.log(this.state.position); } render () { var position = this.state.position; - var supportText = position.is_oppose ? "Opposes" : "Supports"; + if (position.hasOwnProperty('is_oppose') && position.hasOwnProperty('is_support') && position.is_oppose === position.is_support){ + console.log("Both positions true:", this.props.position_we_vote_id) + var supportText = "Unknown"; + } + else if (position.is_oppose){ + var supportText = "Opposes"; + } else if (position.is_support){ + var supportText = "Supports"; + } + return
    {/* One organization"s Position on this Candidate */}
  • @@ -47,7 +58,7 @@ export default class PositionItem extends Component {

    { this.props.speaker_display_name }

    -

    {supportText} { this.props.last_updated }

    +

    {supportText} {this.props.candidate_display_name} { this.props.last_updated }

  • diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx index b281469e5..d0e5accfb 100644 --- a/src/js/components/Ballot/PositionList.jsx +++ b/src/js/components/Ballot/PositionList.jsx @@ -9,8 +9,8 @@ export default class PositionList extends Component { constructor (props) { super(props); - this.state = { - position_list: [] + this.state = { + position_list: [] }; } @@ -38,7 +38,9 @@ export default class PositionList extends Component { position_we_vote_id={item.position_we_vote_id} speaker_display_name={item.speaker_display_name} speaker_image_url_https={item.speaker_image_url_https} - last_updated={item.last_updated}/> ) + last_updated={item.last_updated} + candidate_display_name={this.props.candidate_display_name} + /> ) } ; } diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 5de11219e..345a3bc91 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -109,7 +109,7 @@ export default class Candidate extends Component { */} { - + }
    diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js index 6347e1e69..cbff5ce15 100644 --- a/src/js/stores/PositionStore.js +++ b/src/js/stores/PositionStore.js @@ -27,8 +27,8 @@ const PositionStore = createStore({ retrievePositionByWeVoteId: function (we_vote_id){ PositionAPIWorker.positionRetrieve(we_vote_id, function (res){ - PositionActions.positionRetrieved(we_vote_id, res); - }); + PositionActions.positionRetrieved(res); + }.bind(this)); }, getLocalPositionByWeVoteId: function (we_vote_id){ From 959ba641b90a3816084de9475388960717428971 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Mon, 22 Feb 2016 19:11:30 -0500 Subject: [PATCH 73/92] ItemActionbar cleanup and adding cursor when hovering to indicate that the portion of the bar is clickable. --- src/js/components/ItemActionbar.jsx | 60 +++++++++++-------------- src/sass/components/_itemActionbar.scss | 3 ++ src/sass/main.scss | 2 +- 3 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/js/components/ItemActionbar.jsx b/src/js/components/ItemActionbar.jsx index af85e84e9..c61cd34f0 100644 --- a/src/js/components/ItemActionbar.jsx +++ b/src/js/components/ItemActionbar.jsx @@ -23,7 +23,7 @@ export default class ItemActionbar extends Component { BallotStore.addChangeListener(this.changeListener); } - componentWillUnmount() { + componentWillUnmount () { BallotStore.removeChangeListener(this.changeListener); } @@ -51,42 +51,33 @@ export default class ItemActionbar extends Component { } render () { - return ( + const bold = { fontWeight: "bold" }; + const { is_support, is_oppose } = this.state; + + // support toggle functions + const supportOn = this.supportItem.bind(this); + const supportOff = this.stopSupportingItem.bind(this); + + // oppose toggle functions + const opposeOn = this.opposeItem.bind(this); + const opposeOff = this.stopOpposingItem.bind(this); + + const itemActionBar =
    - {this.state.is_support ? - - - - - Support - - - : - - - - - Support - - - } - {this.state.is_oppose ? - - - - - Oppose + + + + Support - : - - - - - Oppose + + + + + Oppose - } + @@ -94,7 +85,8 @@ export default class ItemActionbar extends Component {  Share -
    - ); +
    ; + + return itemActionBar; } } diff --git a/src/sass/components/_itemActionbar.scss b/src/sass/components/_itemActionbar.scss index c17530878..470102e82 100644 --- a/src/sass/components/_itemActionbar.scss +++ b/src/sass/components/_itemActionbar.scss @@ -1,3 +1,6 @@ +.item-actionbar .inline-phone:hover { + cursor: pointer; +} .item-actionbar, .item-actionbar2 { border-top: 1px solid #DDD; padding:10px 5px; diff --git a/src/sass/main.scss b/src/sass/main.scss index fe6471cd3..b6e435067 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -31,7 +31,7 @@ './components/_badges', './components/_ballotList', './components/_itemActionbar', - './components/_candidate.scss'; + './components/_candidate'; // 6. Page-specific styles @import From 199bbe54a13c33da252e00e06d30fd1cf26eb721 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Mon, 22 Feb 2016 19:32:03 -0500 Subject: [PATCH 74/92] making starAction hover cursor pointer to indicate that it is clickable --- src/js/components/StarAction.jsx | 20 +++++++++----------- src/sass/components/_starAction.scss | 3 +++ src/sass/main.scss | 1 + 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 src/sass/components/_starAction.scss diff --git a/src/js/components/StarAction.jsx b/src/js/components/StarAction.jsx index 8b4d34116..0a57e4780 100644 --- a/src/js/components/StarAction.jsx +++ b/src/js/components/StarAction.jsx @@ -34,7 +34,7 @@ export default class StarAction extends Component { BallotStore.addChangeListener(this.changeListener); } - componentWillUnmount() { + componentWillUnmount () { BallotStore.removeChangeListener(this.changeListener); } @@ -44,15 +44,13 @@ export default class StarAction extends Component { }); } - render() { - return ( - -   - {this.state.is_starred ? : } - - ); + render () { + return +   + {this.state.is_starred ? : } + ; } } diff --git a/src/sass/components/_starAction.scss b/src/sass/components/_starAction.scss new file mode 100644 index 000000000..c871f579e --- /dev/null +++ b/src/sass/components/_starAction.scss @@ -0,0 +1,3 @@ +.star-action:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/src/sass/main.scss b/src/sass/main.scss index 0b53e6eca..ed7b6ac99 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -32,6 +32,7 @@ './components/_ballotList', './components/_itemActionbar', './components/_candidate', + './components/_starAction', './components/_navigator'; // 6. Page-specific styles From 910e7fe58fe74dabb6e52b1bd8b000084b96dbbc Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Mon, 22 Feb 2016 19:42:11 -0500 Subject: [PATCH 75/92] fixing build test script and some lint errors --- .eslintrc | 2 +- package.json | 2 +- src/js/components/Ballot/PositionItem.jsx | 22 ++++++++++++---------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.eslintrc b/.eslintrc index f394cb4fc..8f524243f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -86,7 +86,7 @@ "no-eval": [2], "no-ex-assign": [2], "no-extend-native": [1], - "no-extra-bind": [2], + "no-extra-bind": [1], "no-extra-boolean-cast": [2], "no-extra-semi": [1], "no-fallthrough": [2], diff --git a/package.json b/package.json index b4d69422f..72f02d34e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "repository": "https://github.com/wevote/webapp.git", "scripts": { "lint": "eslint --format stylish --ext .jsx --ext .js src/js", - "test": "babel-node tests/*.js | faucet", + "test": "npm run lint", "start": "gulp", "deps": "npm run deps:missing && npm run deps:extra", "deps:missing": "dependency-check package.json", diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 3eba4290b..63f02d1a8 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -1,11 +1,12 @@ import React, { Component, PropTypes } from "react"; import { Link } from "react-router"; import PositionStore from "../../stores/PositionStore"; -const moment = require('moment'); +const moment = require("moment"); export default class PositionItem extends Component { static propTypes = { position_we_vote_id: PropTypes.string.isRequired, + last_updated: PropTypes.string }; constructor (props) { @@ -31,18 +32,19 @@ export default class PositionItem extends Component { render () { var position = this.state.position; - if (position.hasOwnProperty('is_oppose') && position.hasOwnProperty('is_support') && position.is_oppose === position.is_support){ - console.log("Both positions true:", this.props.position_we_vote_id) - var supportText = "Unknown"; - } - else if (position.is_oppose){ - var supportText = "Opposes"; - } else if (position.is_support){ - var supportText = "Supports"; + var supportText; + + if (position.hasOwnProperty("is_oppose") && position.hasOwnProperty("is_support") && position.is_oppose === position.is_support){ + console.log("Both positions true:", this.props.position_we_vote_id); + supportText = "Unknown"; + } else if (position.is_oppose) { + supportText = "Opposes"; + } else if (position.is_support) { + supportText = "Supports"; } var dateStr = this.props.last_updated; - var dateText = moment(dateStr).startOf('day').fromNow(); + var dateText = moment(dateStr).startOf("day").fromNow(); return
    {/* One organization"s Position on this Candidate */} From f191e53ef672056b558044d8899163df96ead3d3 Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 16:50:44 -0800 Subject: [PATCH 76/92] Add page for valid address with no google civic election number --- src/js/Root.jsx | 3 +++ src/js/routes/Ballot/Candidate.jsx | 1 + src/js/routes/Ballot/EmptyBallot.jsx | 27 +++++++++++++++++++++++++++ src/js/routes/Settings/Location.jsx | 15 ++++++++++----- src/js/stores/VoterStore.js | 12 +++++++++--- 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 src/js/routes/Ballot/EmptyBallot.jsx diff --git a/src/js/Root.jsx b/src/js/Root.jsx index 6e319cbd8..1f557bd05 100644 --- a/src/js/Root.jsx +++ b/src/js/Root.jsx @@ -19,6 +19,7 @@ import Location from "./routes/Settings/Location"; import BallotIndex from "./routes/Ballot/BallotIndex"; import Ballot from "./routes/Ballot/Ballot"; import Candidate from "./routes/Ballot/Candidate"; +import EmptyBallot from "./routes/Ballot/EmptyBallot"; /* Ballot Off-shoot Pages */ import Opinions from "./routes/Opinions"; @@ -83,6 +84,8 @@ const routes = (firstVisit, voter) => + + {/* diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index 345a3bc91..d18a6a957 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -25,6 +25,7 @@ export default class Candidate extends Component { componentDidMount(){ this.changeListener = this._onChange.bind(this); + BallotStore.initialize(function(){console.log("Initialized ballot in background")}); BallotStore.addChangeListener(this.changeListener); var candidate = BallotStore.getOrFetchCandidateByWeVoteId(this.props.params.we_vote_id); if (candidate) { diff --git a/src/js/routes/Ballot/EmptyBallot.jsx b/src/js/routes/Ballot/EmptyBallot.jsx new file mode 100644 index 000000000..cc340e411 --- /dev/null +++ b/src/js/routes/Ballot/EmptyBallot.jsx @@ -0,0 +1,27 @@ +import React, { Component, PropTypes } from 'react'; +import { Link } from 'react-router'; + +export default class EmptyBallot extends Component { + static propTypes = { + }; + + constructor (props) { + super(props); + } + + render () { + return ( +
    +
    +

    + Sorry +

    + + Our data providers don't have ballot data for your address yet. + Please check back 1-2 weeks before your election day + +
    +
    + ); + } +} diff --git a/src/js/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index 22bd6e3b4..32712d599 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -2,6 +2,7 @@ import React, { Component } from "react"; import { Button, ButtonToolbar } from "react-bootstrap"; import HeaderBackNavigation from "../../components/Navigation/HeaderBackNavigation"; import VoterStore from "../../stores/VoterStore"; +import BallotStore from "../../stores/BallotStore"; export default class Location extends Component { constructor (props) { @@ -23,10 +24,14 @@ export default class Location extends Component { saveLocation () { var { location } = this.state; - VoterStore.saveLocation( location, (err) => { - if (err) return console.error(err); - - window.location.href = "/ballot"; + VoterStore.saveLocation( location, (res) => { + if (res){ + this.props.history.push('/ballot'); + } else { + BallotStore.initialize(function(){}); // reinitialize ballot in case old ballot items from old addresses are stored. + this.props.history.push('/ballot/empty'); + } + }, (err) =>{ }); } @@ -52,7 +57,7 @@ export default class Location extends Component { name="address" value={location} className="form-control" - defaultValue="Oakland, CA" + defaultValue="Oakland, CA" />
    diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 2fbb1c169..272b9bc28 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -158,6 +158,7 @@ const VoterStore = createStore({ saveLocation: function (location, callback) { if (typeof location !== "string") throw new Error("missing location to save"); if (callback instanceof Function === false) throw new Error("missing callback function"); + var that = this; $ajax({ type: "GET", @@ -165,9 +166,14 @@ const VoterStore = createStore({ endpoint: "voterAddressSave", success: (res) => { var { text_for_map_search: savedLocation } = res; - - _setLocation(savedLocation); - callback(null, savedLocation); + cookies.setItem('location', savedLocation); + + if (res.success){ // Successfully saved address and found Google Civic Election ID + callback(true, savedLocation); + } else if (res.status.indexOf("GOOGLE_CIVIC_API_ERROR") != -1){ // Saved Address but couldn't find election ID + console.log("No election for the address"); + callback(false, savedLocation); + } }, error: (err) => callback(err, null) }); From d2f3d37322f5d4860afa12dad6c67cda6dcecf81 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Mon, 22 Feb 2016 18:04:04 -0800 Subject: [PATCH 77/92] Fixed text error with I'm. Added VISUAL DESIGN HERE comments back in. --- src/js/components/Header.jsx | 3 ++- src/js/config.js | 8 ++++---- src/js/routes/Guide/PositionList.jsx | 1 + src/js/routes/More/SignIn.jsx | 8 ++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index a1f1e3642..30311ef24 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -123,13 +123,14 @@ export default class Header extends Component { + {/* To be implemented in a coming release { signedIn ?
  • Sign Out
  • : } - + */}
    ; diff --git a/src/js/config.js b/src/js/config.js index 9419046ac..9ca153622 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -1,9 +1,9 @@ // Note that we import these values into "web_app_config" (so we can search for it) module.exports = { - WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", - WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", - // WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", - // WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", + // WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", + // WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", + WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", + WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", DEBUG_MODE: true, // Use 1 or 0 as opposed to true or false diff --git a/src/js/routes/Guide/PositionList.jsx b/src/js/routes/Guide/PositionList.jsx index c44f19287..a5b3b6fc6 100755 --- a/src/js/routes/Guide/PositionList.jsx +++ b/src/js/routes/Guide/PositionList.jsx @@ -9,6 +9,7 @@ import ItemActionbar from "../../components/ItemActionbar"; import ItemActionBar2 from "../../components/ItemActionBar2"; import StarAction from "../../components/StarAction"; +{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/94226088 */} export default class GuidePositionList extends Component { static propTypes = { diff --git a/src/js/routes/More/SignIn.jsx b/src/js/routes/More/SignIn.jsx index 01425cf48..e277e9750 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -105,7 +105,7 @@ export default class SignIn extends Component {
    {voter.signed_in_facebook ? : null}
    - {/* FOR DEBUGGING + {/* FOR DEBUGGING */}
    signed_in_personal: {voter.signed_in_personal ? True : null}
    signed_in_facebook: {voter.signed_in_facebook ? True : null}
    @@ -116,11 +116,11 @@ export default class SignIn extends Component { first_name: {voter.first_name ? {voter.first_name} : null}
    facebook_id: {voter.facebook_id ? {voter.facebook_id} : null}
    - */} +
    - {/* FOR DEBUGGING + {/* FOR DEBUGGING */}
    - */} +
    ); } From 1411dad2abc62a0c0113de4b7f938d89b572bc6f Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Mon, 22 Feb 2016 23:20:13 -0800 Subject: [PATCH 78/92] Fix setState warnings --- src/js/components/Facebook/Main.js | 5 +++-- src/js/components/FollowOrIgnore.jsx | 5 +++-- src/js/components/VoterGuide/VoterGuideItem.jsx | 9 +++++---- src/js/routes/Guide/PositionList.jsx | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/js/components/Facebook/Main.js b/src/js/components/Facebook/Main.js index 6ae42c55a..1e3a8bc5d 100644 --- a/src/js/components/Facebook/Main.js +++ b/src/js/components/Facebook/Main.js @@ -25,11 +25,12 @@ class Main extends React.Component { componentDidMount () { FacebookActionCreators.initFacebook(); - FacebookStore.addChangeListener(() => this._onFacebookChange()); + this.changeListener = this._onFacebookChange(); + FacebookStore.addChangeListener(this.changeListener); } componentWillUnmount () { - FacebookStore.removeChangeListener(this._onFacebookChange); + FacebookStore.removeChangeListener(this.changeListener) } _onFacebookChange () { diff --git a/src/js/components/FollowOrIgnore.jsx b/src/js/components/FollowOrIgnore.jsx index 07267699c..72d3793b1 100644 --- a/src/js/components/FollowOrIgnore.jsx +++ b/src/js/components/FollowOrIgnore.jsx @@ -34,11 +34,12 @@ export default class FollowOrIgnore extends Component { } componentDidMount () { - VoterGuideStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + VoterGuideStore.addChangeListener(this.changeListener); } componentWillUnmount() { - VoterGuideStore.removeChangeListener(this._onChange.bind(this)); + VoterGuideStore.removeChangeListener(this.changeListener); } _onChange () { diff --git a/src/js/components/VoterGuide/VoterGuideItem.jsx b/src/js/components/VoterGuide/VoterGuideItem.jsx index d369c116f..5d1c0879d 100644 --- a/src/js/components/VoterGuide/VoterGuideItem.jsx +++ b/src/js/components/VoterGuide/VoterGuideItem.jsx @@ -28,11 +28,12 @@ export default class VoterGuideItem extends Component { OrganizationFollowed: null, OrganizationIgnored: null }; - VoterGuideStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + VoterGuideStore.addChangeListener(this.changeListener); } componentWillUnmount() { - VoterGuideStore.removeChangeListener(this._onChange.bind(this)); + VoterGuideStore.removeChangeListener(this.changeListener); } _onChange () { @@ -51,8 +52,8 @@ export default class VoterGuideItem extends Component {
      { this.props.voter_guide_display_name } diff --git a/src/js/routes/Guide/PositionList.jsx b/src/js/routes/Guide/PositionList.jsx index a5b3b6fc6..a5c9be121 100755 --- a/src/js/routes/Guide/PositionList.jsx +++ b/src/js/routes/Guide/PositionList.jsx @@ -9,7 +9,7 @@ import ItemActionbar from "../../components/ItemActionbar"; import ItemActionBar2 from "../../components/ItemActionBar2"; import StarAction from "../../components/StarAction"; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/94226088 */} +/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/94226088 */ export default class GuidePositionList extends Component { static propTypes = { From d94da17f20b987a2f54f31198ef69f7bdcf80e2b Mon Sep 17 00:00:00 2001 From: Lisa Cho Date: Tue, 23 Feb 2016 07:06:55 -0800 Subject: [PATCH 79/92] Fix setstate warnings --- src/js/components/Facebook/Main.js | 2 +- src/js/routes/More/SignIn.jsx | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/js/components/Facebook/Main.js b/src/js/components/Facebook/Main.js index 1e3a8bc5d..ee112c251 100644 --- a/src/js/components/Facebook/Main.js +++ b/src/js/components/Facebook/Main.js @@ -25,7 +25,7 @@ class Main extends React.Component { componentDidMount () { FacebookActionCreators.initFacebook(); - this.changeListener = this._onFacebookChange(); + this.changeListener = this._onFacebookChange.bind(this); FacebookStore.addChangeListener(this.changeListener); } diff --git a/src/js/routes/More/SignIn.jsx b/src/js/routes/More/SignIn.jsx index e277e9750..99c004c2a 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -41,17 +41,18 @@ export default class SignIn extends Component { }); FacebookActionCreators.initFacebook(); - FacebookStore.addChangeListener(() => this._onFacebookChange()); - + this.changeListener = this._onFacebookChange.bind(this); + FacebookStore.addChangeListener(this.changeListener); + this.voterListener = this._onVoterStoreChange.bind(this); // console.log("SignIn componentDidMount VoterStore.addChangeListener"); - VoterStore.addChangeListener(this._onVoterStoreChange.bind(this)); + VoterStore.addChangeListener(this.voterListener); } componentWillUnmount () { - FacebookStore.removeChangeListener(this._onFacebookChange); + FacebookStore.removeChangeListener(this.changeListener); // console.log("SignIn componentWillUnmount VoterStore.removeChangeListener"); - VoterStore.removeChangeListener(this._onVoterStoreChange.bind(this)); + VoterStore.removeChangeListener(this.voterListener); } _onVoterStoreChange () { From 3b219d6ac08897c74e5dfb60c377988c899321ee Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Tue, 23 Feb 2016 08:52:23 -0800 Subject: [PATCH 80/92] =?UTF-8?q?Converted=20=E2=80=98Unknown=E2=80=99=20t?= =?UTF-8?q?o=20=E2=80=98Information=20about=E2=80=99.=20Added=20twitter=5F?= =?UTF-8?q?followers=5Fcount=20to=20More=20Opinions=20page=20so=20voters?= =?UTF-8?q?=20have=20some=20indication=20why=20we=20are=20showing=20the=20?= =?UTF-8?q?opinions=20we=20are.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/components/Ballot/PositionItem.jsx | 2 +- src/js/components/MoreMenu.jsx | 2 +- src/js/components/VoterGuide/VoterGuideItem.jsx | 13 +++++++++++++ src/js/config.js | 8 ++++---- src/js/routes/AddFriends.jsx | 1 + src/js/routes/Connect.jsx | 1 + src/js/routes/More/About.jsx | 2 ++ src/js/routes/More/EmailBallot.jsx | 2 ++ src/js/routes/More/OpinionsFollowed.jsx | 4 +++- src/js/routes/Opinions.jsx | 1 + 10 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 63f02d1a8..985c9169b 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -36,7 +36,7 @@ export default class PositionItem extends Component { if (position.hasOwnProperty("is_oppose") && position.hasOwnProperty("is_support") && position.is_oppose === position.is_support){ console.log("Both positions true:", this.props.position_we_vote_id); - supportText = "Unknown"; + supportText = "Information about"; } else if (position.is_oppose) { supportText = "Opposes"; } else if (position.is_support) { diff --git a/src/js/components/MoreMenu.jsx b/src/js/components/MoreMenu.jsx index 7ad8b4683..e93017a2d 100644 --- a/src/js/components/MoreMenu.jsx +++ b/src/js/components/MoreMenu.jsx @@ -41,7 +41,7 @@ export default class MoreMenu extends Component { }
      {/*
    • Print or Email Ballot
    • */} -
    • Opinions I"m Following
    • +
    • Opinions I'm Following
    • My Ballot Location
    • Account Settings
    diff --git a/src/js/components/VoterGuide/VoterGuideItem.jsx b/src/js/components/VoterGuide/VoterGuideItem.jsx index 5d1c0879d..080891178 100644 --- a/src/js/components/VoterGuide/VoterGuideItem.jsx +++ b/src/js/components/VoterGuide/VoterGuideItem.jsx @@ -3,6 +3,12 @@ import FollowOrIgnore from "../../components/FollowOrIgnore"; import VoterGuideActions from '../../actions/VoterGuideActions'; import VoterGuideStore from '../../stores/VoterGuideStore'; +function numberWithCommas(x) { + var parts = x.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} + export default class VoterGuideItem extends Component { static propTypes = { voter_guide_display_name: PropTypes.string, @@ -12,6 +18,7 @@ export default class VoterGuideItem extends Component { voter_guide_owner_type: PropTypes.string, organization_we_vote_id: PropTypes.string, public_figure_we_vote_id: PropTypes.string, + twitter_followers_count: PropTypes.number, last_updated: PropTypes.string, OrganizationFollowed: PropTypes.string, OrganizationIgnored: PropTypes.string @@ -46,6 +53,11 @@ export default class VoterGuideItem extends Component { } render() { + var twitterFollowers; + var twitterFollowersCount = numberWithCommas(this.props.twitter_followers_count); + if (this.props.twitter_followers_count) { + twitterFollowers =
    {twitterFollowersCount} followers on Twitter
    ; + } return (
    @@ -57,6 +69,7 @@ export default class VoterGuideItem extends Component { />   { this.props.voter_guide_display_name } + {twitterFollowers}
    diff --git a/src/js/config.js b/src/js/config.js index 9ca153622..9419046ac 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -1,9 +1,9 @@ // Note that we import these values into "web_app_config" (so we can search for it) module.exports = { - // WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", - // WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", - WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", - WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", + WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", + WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", + // WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", + // WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", DEBUG_MODE: true, // Use 1 or 0 as opposed to true or false diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx index 655364223..9d36e1442 100644 --- a/src/js/routes/AddFriends.jsx +++ b/src/js/routes/AddFriends.jsx @@ -3,6 +3,7 @@ import { Link } from 'react-router'; import { Input } from 'react-bootstrap'; import BottomContinueNavigation from '../components/Navigation/BottomContinueNavigation'; +{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479679 */} export default class AddFriends extends Component { constructor(props) { diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 261518cca..79d1345e2 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -3,6 +3,7 @@ import { Link } from "react-router"; import { Button } from "react-bootstrap"; import OrganizationsToFollowList from "../components/OrganizationsToFollowList"; +{/* VISUAL DESIGN HERE: https://invis.io/E45246B2C */} export default class Connect extends Component { static propTypes = { diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index bfd007029..d1b04eca3 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -1,6 +1,8 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; +{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/90192590 */} + export default class About extends Component { constructor(props) { super(props); diff --git a/src/js/routes/More/EmailBallot.jsx b/src/js/routes/More/EmailBallot.jsx index 581b87266..dadc551d2 100755 --- a/src/js/routes/More/EmailBallot.jsx +++ b/src/js/routes/More/EmailBallot.jsx @@ -3,6 +3,8 @@ import { Button, ButtonToolbar, Input } from "react-bootstrap"; import { Link } from "react-router"; import Main from '../../components/Facebook/Main'; +{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479656 */} + export default class EmailBallot extends Component { constructor(props) { super(props); diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index 86ba4ed10..b27f5ea11 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -4,6 +4,8 @@ import HeaderBackNavigation from "../../components/Navigation/HeaderBackNavigati import VoterGuideStore from "../../stores/VoterGuideStore"; import VoterGuideItem from "../../components/VoterGuide/VoterGuideItem"; +{/* VISUAL DESIGN HERE: https://invis.io/8F53FDX9G */} + export default class OpinionsFollowed extends Component { static propTypes = { children: PropTypes.object @@ -28,7 +30,7 @@ export default class OpinionsFollowed extends Component { return (
    -

    Opinions I"m Following

    +

    Opinions I'm Following

    {/*
    diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 5f929ae2c..b8ceec15b 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -4,6 +4,7 @@ import HeaderBackNavigation from "../Components/Navigation/HeaderBackNavigation" import VoterGuideStore from "../stores/VoterGuideStore"; import VoterGuideItem from "../components/VoterGuide/VoterGuideItem"; +{/* VISUAL DESIGN HERE: https://invis.io/TR4A1NYAQ */} export default class Opinions extends Component { static propTypes = { From 59883084db596a8d09ac1360991b280b452adfb5 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 12:02:30 -0500 Subject: [PATCH 81/92] making mouse change to cursor pointer when hovering hamburger icon to indicate that its clickable --- src/js/components/Header.jsx | 2 +- src/js/config.js | 2 +- src/js/routes/Opinions.jsx | 49 ++++++++++++++++++-------------- src/js/stores/VoterGuideStore.js | 17 +---------- src/js/utils/service.js | 4 +-- src/sass/components/_header.scss | 3 ++ src/sass/main.scss | 3 +- 7 files changed, 37 insertions(+), 43 deletions(-) create mode 100644 src/sass/components/_header.scss diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 30311ef24..c21d4e5da 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -53,7 +53,7 @@ export default class Header extends Component { // image = ; const header = -
    +

    diff --git a/src/js/config.js b/src/js/config.js index 9ca153622..2c7bc5369 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -3,7 +3,7 @@ module.exports = { // WE_VOTE_SERVER_ADMIN_ROOT_URL: "https://api.wevoteusa.org/admin/", // WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", WE_VOTE_SERVER_ADMIN_ROOT_URL: "http://localhost:8000/admin/", - WE_VOTE_SERVER_API_ROOT_URL: "http://localhost:8000/apis/v1/", + WE_VOTE_SERVER_API_ROOT_URL: "https://api.wevoteusa.org/apis/v1/", DEBUG_MODE: true, // Use 1 or 0 as opposed to true or false diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 5f929ae2c..af129c471 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -1,6 +1,4 @@ import React, {Component, PropTypes } from "react"; -import HeaderBackNavigation from "../Components/Navigation/HeaderBackNavigation"; - import VoterGuideStore from "../stores/VoterGuideStore"; import VoterGuideItem from "../components/VoterGuide/VoterGuideItem"; @@ -10,17 +8,23 @@ export default class Opinions extends Component { children: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); - this.state = {}; + this.state = { + loading: true + }; } componentDidMount () { VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); } - render() { - return ( + render () { + const { voter_guide_list } = this.state; + + console.log(voter_guide_list); + + const opinions =

    More Opinions I Can Follow

    @@ -28,22 +32,23 @@ export default class Opinions extends Component { */} -

    These organizations and public figures have opinions about items on your - ballot. Click the "Follow" button to pay attention to them.

    - -
    - { - this.state.voter_guide_list ? - this.state.voter_guide_list.map( item => - - ) : (
    - -

    Loading ... One Moment

    -
    ) - } -
    +

    + These organizations and public figures have opinions about items on your + ballot. Click the "Follow" button to pay attention to them. +

    + +
    + { voter_guide_list ? voter_guide_list + .map( item => ) : +
    + +

    Loading ... One Moment

    +
    + } +
    -
    - ); +

    ; + + return opinions; } } diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 9e346df29..16b3a2f59 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -1,6 +1,6 @@ import BallotStore from "../stores/BallotStore"; import { createStore } from "../utils/createStore"; -import {shallowClone} from "../utils/object-utils"; +import { shallowClone } from "../utils/object-utils"; const AppDispatcher = require("../dispatcher/AppDispatcher"); const VoterGuideConstants = require("../constants/VoterGuideConstants"); @@ -19,11 +19,6 @@ let _voter_guides_to_follow_list = []; // A summary of all voter guides to follo let _voter_guides_followed_order = []; let _voter_guides_followed_list = []; // A summary of voter guides already followed (list of voter guide we_vote_id's) -const MEASURE = "MEASURE"; - -function printErr (err) { - console.error(err); -} //.query({ ballot_item_we_vote_id: "wv01cand2968" }) //.query({ kind_of_ballot_item: "CANDIDATE" }) @@ -194,16 +189,6 @@ function stopFollowingOrganization (we_vote_id) { ); } -// Refactor to this structure? -//const VoterGuideAPIWorker = { -// voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success) { -// return get({ -// endpoint: "voterBallotItemsRetrieveFromGoogleCivic", -// query: {text_for_map_search}, success: success || defaultSuccess -// }); -// } -//}; - const VoterGuideStore = createStore({ /** * initialize the voter guide store with "guides to follow" data, if no data diff --git a/src/js/utils/service.js b/src/js/utils/service.js index aca78ee5b..3103441e9 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -22,9 +22,9 @@ const defaults = { query: {}, type: "GET", data: function () { - return { + return cookies.getItem("voter_device_id") ? { voter_device_id: cookies.getItem("voter_device_id") - }; + } : {}; }, success: (res) => console.warn("Success function not defined:", res), error: (err) => console.error(err.message) diff --git a/src/sass/components/_header.scss b/src/sass/components/_header.scss new file mode 100644 index 000000000..46a3b8d80 --- /dev/null +++ b/src/sass/components/_header.scss @@ -0,0 +1,3 @@ +.header .glyphicon-menu-hamburger:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/src/sass/main.scss b/src/sass/main.scss index ed7b6ac99..8be4d8c9c 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -33,7 +33,8 @@ './components/_itemActionbar', './components/_candidate', './components/_starAction', - './components/_navigator'; + './components/_navigator', + './components/_header'; // 6. Page-specific styles @import From fbe4285656af6c98f01000c88b5cc249c4a3109d Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 14:02:56 -0500 Subject: [PATCH 82/92] opinions update --- src/js/components/VoterGuide/GuideList.jsx | 36 ++++++++ src/js/components/VoterGuide/Organization.jsx | 22 +++++ src/js/routes/Opinions.jsx | 88 ++++++++++++++----- src/js/stores/BallotStore.js | 2 + src/js/stores/VoterGuideStore.js | 1 + src/js/stores/VoterStore.js | 4 + 6 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 src/js/components/VoterGuide/GuideList.jsx create mode 100644 src/js/components/VoterGuide/Organization.jsx diff --git a/src/js/components/VoterGuide/GuideList.jsx b/src/js/components/VoterGuide/GuideList.jsx new file mode 100644 index 000000000..5c32d704d --- /dev/null +++ b/src/js/components/VoterGuide/GuideList.jsx @@ -0,0 +1,36 @@ +import React, { Component, PropTypes } from "react"; +import Organization from "./Organization"; + +export default class GuideList extends Component { + + static propTypes = { + organizations: PropTypes.array + }; + + constructor (props) { + super(props); + } + + render () { + const orgs = this.props.organizations.map( org => { + + var { + organization_we_vote_id: id, + voter_guide_display_name: displayName, + voter_guide_image_url: imageUrl, + twitter_followers_count: followers + } = org; + + return ; + + }); + + const guideList = +
    + {orgs} +
    ; + + return guideList; + } + +} diff --git a/src/js/components/VoterGuide/Organization.jsx b/src/js/components/VoterGuide/Organization.jsx new file mode 100644 index 000000000..1b7755ed1 --- /dev/null +++ b/src/js/components/VoterGuide/Organization.jsx @@ -0,0 +1,22 @@ +import React, { Component, PropTypes } from "react"; + +export default class Organization extends Component { + static propTypes = { + id: PropTypes.string, + imageUrl: PropTypes.string, + displayName: PropTypes.string, + followers: PropTypes.string, + }; + + constructor (props) { + super(props); + } + + render () { + const org = +
    ; + + return org; + } + +} diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 1eba2bdb8..c098edb2a 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -1,8 +1,10 @@ import React, {Component, PropTypes } from "react"; -import VoterGuideStore from "../stores/VoterGuideStore"; -import VoterGuideItem from "../components/VoterGuide/VoterGuideItem"; +import { $ajax } from "../utils/service"; -{/* VISUAL DESIGN HERE: https://invis.io/TR4A1NYAQ */} +import BallotStore from "../stores/BallotStore"; +import GuideList from "../components/VoterGuide/GuideList"; + +/* VISUAL DESIGN HERE: https://invis.io/TR4A1NYAQ */ export default class Opinions extends Component { static propTypes = { @@ -11,21 +13,74 @@ export default class Opinions extends Component { constructor (props) { super(props); + this.state = { - loading: true + loading: true, + error: false }; + + this.electionId = BallotStore.getGoogleCivicElectionId(); + } componentDidMount () { - VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); + + if (! this.electionId ) + this.setState({ loading: false, error: false }); + + else + $ajax({ + endpoint: "voterGuidesToFollowRetrieve", + data: { "google_civic_election_id": this.electionId }, + + success: (res) => { + this.guideList = res.voter_guides; + this.setState({ loading: false }); + console.log(res); + }, + + error: (err) => { + console.error(err); + + this.setState({ + loading: false, + error: true + }); + } + }); + // VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); } render () { - const { voter_guide_list } = this.state; + const { loading, error } = this.state; + const EMPTY_TEXT = "You do not have a ballot yet!"; + const { guideList, electionId } = this; - console.log(voter_guide_list); - const opinions = + let opinions; + + if ( !electionId ) + opinions = EMPTY_TEXT; + + else + if (loading) + opinions = +
    + +

    Loading ... One Moment

    +
    ; + + else if (error) + opinions = "Error loading your organizations"; + + else if (guideList.length > 0) + opinions = ; + + + else + opinions = EMPTY_TEXT; + + const content =

    More Opinions I Can Follow

    @@ -34,22 +89,15 @@ export default class Opinions extends Component { placeholder="Search by name or twitter handle." /> */}

    - These organizations and public figures have opinions about items on your - ballot. Click the "Follow" button to pay attention to them. + These organizations and public figures have opinions about items on + your ballot. Click the "Follow" button to pay attention to them.

    -
    - { voter_guide_list ? voter_guide_list - .map( item => ) : -
    - -

    Loading ... One Moment

    -
    - } -
    + {opinions} +
    ; - return opinions; + return content; } } diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 3b5177835..184a33f8e 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -199,6 +199,8 @@ const BallotStore = createStore({ .voterBallotItemsRetrieve() .then( (res) => { + _google_civic_election_id = res.google_civic_election_id; + addItemsToBallotStore( res.ballot_item_list ); diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 16b3a2f59..98f510ff1 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -23,6 +23,7 @@ let _voter_guides_followed_list = []; // A summary of voter guides already follo //.query({ ballot_item_we_vote_id: "wv01cand2968" }) //.query({ kind_of_ballot_item: "CANDIDATE" }) function retrieveVoterGuidesToFollowList () { + console.log(BallotStore.getGoogleCivicElectionId()); return new Promise( (resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}voterGuidesToFollowRetrieve/`) .withCredentials() diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index 272b9bc28..c72a2a4e2 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -15,6 +15,10 @@ let _voter = {}; const VoterStore = createStore({ + deviceId: function () { + return _voter_device_id; + }, + hasDeviceId: function () { return _voter_device_id ? true : false; }, From 3ab788a98c065f19fd958d99b18780f46a925071 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 15:38:16 -0500 Subject: [PATCH 83/92] Opinions fix --- package.json | 3 +- src/js/components/Follow.jsx | 12 ++++++ src/js/components/Ignore.jsx | 12 ++++++ src/js/components/LoadingWheel.jsx | 9 ++++ src/js/components/VoterGuide/GuideList.jsx | 9 +++- src/js/components/VoterGuide/Organization.jsx | 42 ++++++++++++++++++- src/js/routes/Opinions.jsx | 8 ++-- 7 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 src/js/components/Follow.jsx create mode 100644 src/js/components/Ignore.jsx create mode 100644 src/js/components/LoadingWheel.jsx diff --git a/package.json b/package.json index 72f02d34e..2b86e9655 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "object-assign": "^4.0.1", "react": "^0.14.3", "react-dom": "^0.14.3", - "superagent": "^1.5.0" + "superagent": "^1.5.0", + "underscore": "^1.8.3" }, "devDependencies": { "autoprefixer-loader": "^3.1.0", diff --git a/src/js/components/Follow.jsx b/src/js/components/Follow.jsx new file mode 100644 index 000000000..7a637656b --- /dev/null +++ b/src/js/components/Follow.jsx @@ -0,0 +1,12 @@ +import React, { Component, PropTypes } from "react"; + +export default class Follow extends Component { + + static propTypes = { + id: PropTypes.string + }; + + render () { + return
    ; + } +} diff --git a/src/js/components/Ignore.jsx b/src/js/components/Ignore.jsx new file mode 100644 index 000000000..86ad2e440 --- /dev/null +++ b/src/js/components/Ignore.jsx @@ -0,0 +1,12 @@ +import React, { Component, PropTypes } from "react"; + +export default class Ignore extends Component { + + static propTypes = { + id: PropTypes.string + }; + + render () { + return
    ; + } +} diff --git a/src/js/components/LoadingWheel.jsx b/src/js/components/LoadingWheel.jsx new file mode 100644 index 000000000..14b3832fb --- /dev/null +++ b/src/js/components/LoadingWheel.jsx @@ -0,0 +1,9 @@ +import React from "react"; + +const LoadingWheel = +
    + +

    Loading ... One Moment

    +
    ; + +export default LoadingWheel; diff --git a/src/js/components/VoterGuide/GuideList.jsx b/src/js/components/VoterGuide/GuideList.jsx index 5c32d704d..7a8483f8c 100644 --- a/src/js/components/VoterGuide/GuideList.jsx +++ b/src/js/components/VoterGuide/GuideList.jsx @@ -12,7 +12,8 @@ export default class GuideList extends Component { } render () { - const orgs = this.props.organizations.map( org => { + + let orgs = this.props.organizations.map( (org, i) => { var { organization_we_vote_id: id, @@ -21,7 +22,11 @@ export default class GuideList extends Component { twitter_followers_count: followers } = org; - return ; + // Key can be id once issue #85 is resolved on server + // https://github.com/wevote/WeVoteServer/issues/85 + const key = id + "-" + i; + + return ; }); diff --git a/src/js/components/VoterGuide/Organization.jsx b/src/js/components/VoterGuide/Organization.jsx index 1b7755ed1..bcdf493d3 100644 --- a/src/js/components/VoterGuide/Organization.jsx +++ b/src/js/components/VoterGuide/Organization.jsx @@ -1,11 +1,23 @@ import React, { Component, PropTypes } from "react"; +import Follow from "../Follow"; +import Ignore from "../Ignore"; + +function numberWithCommas (num) { + var parts = num.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} + +const img = { float: "left", marginRight: ".1in" }; +const stretch = { width: "100%" }; export default class Organization extends Component { static propTypes = { id: PropTypes.string, + key: PropTypes.string, imageUrl: PropTypes.string, displayName: PropTypes.string, - followers: PropTypes.string, + followers: PropTypes.number, }; constructor (props) { @@ -13,8 +25,34 @@ export default class Organization extends Component { } render () { + + const { + id, + displayName, + imageUrl, + followers + } = this.props; + + const followCount = + + {numberWithCommas(followers)} followers on Twitter + ; + + // + const org = -
    ; +
    +
    + +
    +
    {displayName}
    +
    {followCount}
    + + +
    +
    +
    ; return org; } diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index c098edb2a..9b6c77fe0 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -1,6 +1,8 @@ import React, {Component, PropTypes } from "react"; import { $ajax } from "../utils/service"; +import LoadingWheel from "../components/LoadingWheel"; + import BallotStore from "../stores/BallotStore"; import GuideList from "../components/VoterGuide/GuideList"; @@ -56,7 +58,6 @@ export default class Opinions extends Component { const EMPTY_TEXT = "You do not have a ballot yet!"; const { guideList, electionId } = this; - let opinions; if ( !electionId ) @@ -65,10 +66,7 @@ export default class Opinions extends Component { else if (loading) opinions = -
    - -

    Loading ... One Moment

    -
    ; + LoadingWheel; else if (error) opinions = "Error loading your organizations"; From f9c6724576fcce1437e6bd12d4cfd5fb31d46469 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 15:49:16 -0500 Subject: [PATCH 84/92] using centered LoadingWheel for ballot page load --- src/js/routes/Ballot/Ballot.jsx | 48 ++++++++++++++++----------------- src/js/routes/Opinions.jsx | 15 +++++------ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 5ea84c5e1..178e61aee 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -1,12 +1,13 @@ -import React, { Component, PropTypes } from 'react'; -import { Link } from 'react-router'; +import React, { Component, PropTypes } from "react"; +import LoadingWheel from "../../components/LoadingWheel"; + +import BallotStore from "../../stores/BallotStore"; +import BallotItem from "../../components/Ballot/BallotItem"; -import BallotStore from '../../stores/BallotStore'; -import BallotItem from '../../components/Ballot/BallotItem'; -import BallotActions from '../../actions/BallotActions'; export default class Ballot extends Component { static propTypes = { + history: PropTypes.array, children: PropTypes.object }; @@ -16,30 +17,27 @@ export default class Ballot extends Component { } componentDidMount () { - BallotStore.initialize( function(ballot_list){ - if (ballot_list.length === 0){ - this.props.history.push('settings/location'); - } else { - this.setState({ballot_list}); - } - }.bind(this)); + BallotStore.initialize( (ballot_list) => { + + if (ballot_list.length === 0) + this.props.history.push("settings/location"); + + else + this.setState({ ballot_list }); + + }); } render () { var { ballot_list } = this.state; - return (
    - { - ballot_list ? ballot_list - .map( item => - - ) : ( -
    - -

    Loading ... One Moment

    -
    - ) - } -
    ); + const ballot = +
    + { ballot_list ? ballot_list.map( item => + + ) : LoadingWheel } +
    ; + + return ballot; } } diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 9b6c77fe0..f143384e8 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -10,19 +10,20 @@ import GuideList from "../components/VoterGuide/GuideList"; export default class Opinions extends Component { static propTypes = { + history: PropTypes.array, children: PropTypes.object }; constructor (props) { super(props); - this.state = { - loading: true, - error: false - }; + this.state = { loading: true, error: false }; this.electionId = BallotStore.getGoogleCivicElectionId(); + if ( !this.electionId ) + this.props.history.push("/ballot"); + } componentDidMount () { @@ -43,11 +44,7 @@ export default class Opinions extends Component { error: (err) => { console.error(err); - - this.setState({ - loading: false, - error: true - }); + this.setState({ loading: false, error: true }); } }); // VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); From ad69d95ecb5d978403e724ad6b87fc43f97a3fae Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 19:09:55 -0500 Subject: [PATCH 85/92] first commit --- package.json | 1 + src/js/components/Follow.jsx | 12 ---- src/js/components/Ignore.jsx | 12 ---- src/js/components/VoterGuide/GuideList.jsx | 57 +++++++++++++++++-- src/js/components/VoterGuide/Organization.jsx | 34 ++++------- src/js/routes/Ballot/Ballot.jsx | 2 +- src/js/routes/Opinions.jsx | 39 +++++-------- src/js/stores/GuideStore.js | 22 +++++++ src/sass/components/_ignore.scss | 3 + src/sass/components/_organization.scss | 17 ++++++ src/sass/main.scss | 2 + 11 files changed, 124 insertions(+), 77 deletions(-) delete mode 100644 src/js/components/Follow.jsx delete mode 100644 src/js/components/Ignore.jsx create mode 100644 src/js/stores/GuideStore.js create mode 100644 src/sass/components/_ignore.scss create mode 100644 src/sass/components/_organization.scss diff --git a/package.json b/package.json index 2b86e9655..6041edde2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "moment": "^2.11.2", "object-assign": "^4.0.1", "react": "^0.14.3", + "react-addons-css-transition-group": "^0.14.7", "react-dom": "^0.14.3", "superagent": "^1.5.0", "underscore": "^1.8.3" diff --git a/src/js/components/Follow.jsx b/src/js/components/Follow.jsx deleted file mode 100644 index 7a637656b..000000000 --- a/src/js/components/Follow.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { Component, PropTypes } from "react"; - -export default class Follow extends Component { - - static propTypes = { - id: PropTypes.string - }; - - render () { - return
    ; - } -} diff --git a/src/js/components/Ignore.jsx b/src/js/components/Ignore.jsx deleted file mode 100644 index 86ad2e440..000000000 --- a/src/js/components/Ignore.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { Component, PropTypes } from "react"; - -export default class Ignore extends Component { - - static propTypes = { - id: PropTypes.string - }; - - render () { - return
    ; - } -} diff --git a/src/js/components/VoterGuide/GuideList.jsx b/src/js/components/VoterGuide/GuideList.jsx index 7a8483f8c..9335313a8 100644 --- a/src/js/components/VoterGuide/GuideList.jsx +++ b/src/js/components/VoterGuide/GuideList.jsx @@ -1,6 +1,9 @@ import React, { Component, PropTypes } from "react"; +import ReactCSSTransitionGroup from "react-addons-css-transition-group"; import Organization from "./Organization"; +import GuideStore from "../../stores/GuideStore"; + export default class GuideList extends Component { static propTypes = { @@ -9,11 +12,45 @@ export default class GuideList extends Component { constructor (props) { super(props); + + var { organizations: orgs } = this.props; + + /** + * this is being done because of issue #85... + * https://github.com/wevote/WeVoteServer/issues/85 + + * READ: + * -- GuideStore is naturally unique per item key + * -- so... store data and then convert it to an array + + * SEE BELOW: + * GuideStore.addOrganization + * then -> + * GuideStore.toArray() + + * this is inefficient but it needs to be done to create unique + * organizations in the list because the backend is not unique. + */ + + orgs.forEach(GuideStore.addOrganization); + this.state = { orgList: GuideStore.toArray() }; + + } + + /** + * when a user clicks ignore, make the org disappear + * @param {Integer} i index in array of the item clicked + */ + handleIgnore (i) { + console.log(i); + var newOrgList = this.state.orgList.slice(); + newOrgList.splice(i, 1); + this.setState({ orgList: newOrgList }); } render () { - let orgs = this.props.organizations.map( (org, i) => { + let orgs = this.state.orgList.map( (org, i) => { var { organization_we_vote_id: id, @@ -26,13 +63,25 @@ export default class GuideList extends Component { // https://github.com/wevote/WeVoteServer/issues/85 const key = id + "-" + i; - return ; + const organization = + + + + ; + + return organization; }); const guideList = -
    - {orgs} +
    + + {orgs} +
    ; return guideList; diff --git a/src/js/components/VoterGuide/Organization.jsx b/src/js/components/VoterGuide/Organization.jsx index bcdf493d3..96ceb585b 100644 --- a/src/js/components/VoterGuide/Organization.jsx +++ b/src/js/components/VoterGuide/Organization.jsx @@ -1,6 +1,4 @@ import React, { Component, PropTypes } from "react"; -import Follow from "../Follow"; -import Ignore from "../Ignore"; function numberWithCommas (num) { var parts = num.toString().split("."); @@ -8,9 +6,6 @@ function numberWithCommas (num) { return parts.join("."); } -const img = { float: "left", marginRight: ".1in" }; -const stretch = { width: "100%" }; - export default class Organization extends Component { static propTypes = { id: PropTypes.string, @@ -18,6 +13,7 @@ export default class Organization extends Component { imageUrl: PropTypes.string, displayName: PropTypes.string, followers: PropTypes.number, + children: PropTypes.array }; constructor (props) { @@ -27,29 +23,21 @@ export default class Organization extends Component { render () { const { - id, displayName, imageUrl, - followers + followers, } = this.props; - const followCount = - - {numberWithCommas(followers)} followers on Twitter - ; - - // - const org = -
    -
    - -
    -
    {displayName}
    -
    {followCount}
    - - +
    +
    + {displayName +
    +

    {displayName}

    +

    {numberWithCommas(followers)} Twitter Followers

    +

    + {this.props.children} +

    ; diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 178e61aee..e61948389 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -7,7 +7,7 @@ import BallotItem from "../../components/Ballot/BallotItem"; export default class Ballot extends Component { static propTypes = { - history: PropTypes.array, + history: PropTypes.object, children: PropTypes.object }; diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index f143384e8..8fc1e73a8 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -10,26 +10,20 @@ import GuideList from "../components/VoterGuide/GuideList"; export default class Opinions extends Component { static propTypes = { - history: PropTypes.array, + history: PropTypes.object, children: PropTypes.object }; constructor (props) { super(props); - this.state = { loading: true, error: false }; - this.electionId = BallotStore.getGoogleCivicElectionId(); - - if ( !this.electionId ) - this.props.history.push("/ballot"); - } componentDidMount () { if (! this.electionId ) - this.setState({ loading: false, error: false }); + this.props.history.push("/ballot"); else $ajax({ @@ -47,49 +41,44 @@ export default class Opinions extends Component { this.setState({ loading: false, error: true }); } }); - // VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); + } render () { - const { loading, error } = this.state; const EMPTY_TEXT = "You do not have a ballot yet!"; + + const { loading, error } = this.state; const { guideList, electionId } = this; - let opinions; + let guides; if ( !electionId ) - opinions = EMPTY_TEXT; + guides = EMPTY_TEXT; else if (loading) - opinions = + guides = LoadingWheel; else if (error) - opinions = "Error loading your organizations"; + guides = "Error loading your organizations"; - else if (guideList.length > 0) - opinions = ; + else if (guideList instanceof Array && guideList.length > 0) + guides = ; else - opinions = EMPTY_TEXT; + guides = EMPTY_TEXT; const content = -
    +

    More Opinions I Can Follow

    - {/* - - */}

    These organizations and public figures have opinions about items on your ballot. Click the "Follow" button to pay attention to them.

    - - {opinions} - + {guides}
    ; diff --git a/src/js/stores/GuideStore.js b/src/js/stores/GuideStore.js new file mode 100644 index 000000000..6d08d9739 --- /dev/null +++ b/src/js/stores/GuideStore.js @@ -0,0 +1,22 @@ +import { createStore } from "../utils/createStore"; + +const _guide_store = {}; + +const GuideStore = createStore({ + addOrganization: function (org) { + _guide_store[org.organization_we_vote_id] = org; + }, + + toArray: function () { + var t = []; + + Object.keys(_guide_store).forEach( (id) => { + if (_guide_store.hasOwnProperty(id)) + t.push(_guide_store[id]); + }); + + return t; + } +}); + +export default GuideStore; diff --git a/src/sass/components/_ignore.scss b/src/sass/components/_ignore.scss new file mode 100644 index 000000000..b3a8b9ef1 --- /dev/null +++ b/src/sass/components/_ignore.scss @@ -0,0 +1,3 @@ +.ignore { + margin-left: .1in; +} \ No newline at end of file diff --git a/src/sass/components/_organization.scss b/src/sass/components/_organization.scss new file mode 100644 index 000000000..213a147b3 --- /dev/null +++ b/src/sass/components/_organization.scss @@ -0,0 +1,17 @@ +.org-ignore-enter { + opacity: 0.01; +} + +.org-ignore-enter.org-ignore-enter-active { + opacity: 1; + transition: opacity 500ms ease-in; +} + +.org-ignore-leave { + opacity: 1; +} + +.org-ignore-leave.org-ignore-leave-active { + opacity: 0.01; + transition: opacity 300ms ease-in; +} \ No newline at end of file diff --git a/src/sass/main.scss b/src/sass/main.scss index 8be4d8c9c..d8bbd231e 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -34,6 +34,8 @@ './components/_candidate', './components/_starAction', './components/_navigator', + './components/_ignore', + './components/_organization', './components/_header'; // 6. Page-specific styles From 377e227747ec5b8b87b036b2f404bc71299cf986 Mon Sep 17 00:00:00 2001 From: nick fiorini Date: Tue, 23 Feb 2016 22:39:26 -0500 Subject: [PATCH 86/92] finishing up GuideStore follow/ignore actions --- src/js/actions/GuideActions.js | 24 ++++ src/js/components/VoterGuide/GuideList.jsx | 34 ++++- src/js/constants/GuideConstants.js | 4 + src/js/stores/GuideStore.js | 56 ++++++++ src/js/utils/service.js | 149 --------------------- src/sass/components/_organization.scss | 3 + 6 files changed, 116 insertions(+), 154 deletions(-) create mode 100644 src/js/actions/GuideActions.js create mode 100644 src/js/constants/GuideConstants.js diff --git a/src/js/actions/GuideActions.js b/src/js/actions/GuideActions.js new file mode 100644 index 000000000..2716ae666 --- /dev/null +++ b/src/js/actions/GuideActions.js @@ -0,0 +1,24 @@ +import AppDispatcher from "../dispatcher/AppDispatcher"; +import GuideConstants from "../constants/GuideConstants"; + +const GuideActions = { + /** + * @param {String} id to ignore + */ + ignore: function (id) { + AppDispatcher.dispatch({ + actionType: GuideConstants.ORG_IGNORE, id + }); + }, + + /** + * @param {String} id to follow + */ + follow: function (id) { + AppDispatcher.dispatch({ + actionType: GuideConstants.ORG_FOLLOW, id + }); + } +}; + +export default GuideActions; diff --git a/src/js/components/VoterGuide/GuideList.jsx b/src/js/components/VoterGuide/GuideList.jsx index 9335313a8..cb39bb795 100644 --- a/src/js/components/VoterGuide/GuideList.jsx +++ b/src/js/components/VoterGuide/GuideList.jsx @@ -3,6 +3,7 @@ import ReactCSSTransitionGroup from "react-addons-css-transition-group"; import Organization from "./Organization"; import GuideStore from "../../stores/GuideStore"; +import GuideActions from "../../actions/GuideActions"; export default class GuideList extends Component { @@ -37,15 +38,38 @@ export default class GuideList extends Component { } + componentDidMount () { + GuideStore.addChangeListener(this.storeChange.bind(this)); + } + + componentWillUnmount () { + GuideStore.removeChangeListener(this.storeChange.bind(this)); + } + + storeChange () { + this.setState({ orgList: GuideStore.toArray() }); + } + /** * when a user clicks ignore, make the org disappear * @param {Integer} i index in array of the item clicked */ handleIgnore (i) { - console.log(i); - var newOrgList = this.state.orgList.slice(); - newOrgList.splice(i, 1); - this.setState({ orgList: newOrgList }); + + var { + organization_we_vote_id: id + } = this.state.orgList.slice().splice(i, 1)[0]; + + GuideActions.ignore(id); + + } + + handleFollow (i) { + var { + organization_we_vote_id: id + } = this.state.orgList.slice().splice(i, 1)[0]; + + GuideActions.follow(id); } render () { @@ -65,7 +89,7 @@ export default class GuideList extends Component { const organization = - - -

    Friends can see what you support and oppose. We never sell emails.
    -

    - -

    Follow More Opinions

    - - - -

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.
    -

    - - {/* - - - -
    - */} -
    + return
    +
    +

    Add Friends

    + + + +

    Friends can see what you support and oppose. We never sell emails.
    +

    + +

    Follow More Opinions

    + + + +

    Find voter guides you can follow. These voter guides have been created by nonprofits, public figures, your friends, and more.
    +

    + + {/* + + + +
    + */}
    - ); +
    ; } } diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index d1b04eca3..86bcf0e8a 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -1,20 +1,19 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router'; +import React, { Component } from "react"; +import { Link } from "react-router"; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/90192590 */} +/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/90192590 */ export default class About extends Component { - constructor(props) { + constructor (props) { super(props); } - static getProps() { + static getProps () { return {}; } - render() { - return ( -
    + render () { + return

    About We Vote

    @@ -85,7 +84,6 @@ export default class About extends Component {

    -
    - ); +
    ; } } diff --git a/src/js/routes/More/EmailBallot.jsx b/src/js/routes/More/EmailBallot.jsx index dadc551d2..f4ca423e1 100755 --- a/src/js/routes/More/EmailBallot.jsx +++ b/src/js/routes/More/EmailBallot.jsx @@ -1,31 +1,32 @@ import React, { Component } from "react"; -import { Button, ButtonToolbar, Input } from "react-bootstrap"; +import { Button, Input } from "react-bootstrap"; import { Link } from "react-router"; -import Main from '../../components/Facebook/Main'; +import Main from "../../components/Facebook/Main"; -{/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479656 */} +/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479656 */ export default class EmailBallot extends Component { - constructor(props) { + constructor (props) { super(props); } - static getProps() { + static getProps () { return {}; } - render() { - return ( + render () { + + const emailBallot =

    Print, Save or Email Ballot


    - Email your ballot to yourself so you can print it, or come back to it later. We will never sell your email address. @@ -47,7 +48,8 @@ export default class EmailBallot extends Component {
    -
    - ); +
    ; + + return emailBallot; } } diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index b27f5ea11..88101a2ed 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -1,34 +1,34 @@ import React, {Component, PropTypes } from "react"; -import HeaderBackNavigation from "../../components/Navigation/HeaderBackNavigation"; import VoterGuideStore from "../../stores/VoterGuideStore"; import VoterGuideItem from "../../components/VoterGuide/VoterGuideItem"; -{/* VISUAL DESIGN HERE: https://invis.io/8F53FDX9G */} +import LoadingWheel from "../../components/LoadingWheel"; + +/* VISUAL DESIGN HERE: https://invis.io/8F53FDX9G */ export default class OpinionsFollowed extends Component { static propTypes = { children: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); this.state = {}; } componentDidMount () { - VoterGuideStore.initializeGuidesFollowed( function(voter_guide_followed_list) { + VoterGuideStore.initializeGuidesFollowed( function (voter_guide_followed_list) { if (voter_guide_followed_list !== undefined && voter_guide_followed_list.length > 0){ this.setState({ voter_guide_followed_list }); } else { - this.props.history.push('/opinions'); + this.props.history.push("/opinions"); } }.bind(this)); } - render() { - return ( -
    + render () { + return

    Opinions I'm Following

    {/* @@ -40,15 +40,11 @@ export default class OpinionsFollowed extends Component { this.state.voter_guide_followed_list ? this.state.voter_guide_followed_list.map( item => - ) : (
    - -

    Loading ... One Moment

    -
    - ) + ) : LoadingWheel + }
    -
    - ); +
    ; } } diff --git a/src/js/stores/GuideStore.js b/src/js/stores/GuideStore.js index f1d6a5fb6..203a45660 100644 --- a/src/js/stores/GuideStore.js +++ b/src/js/stores/GuideStore.js @@ -8,7 +8,6 @@ const _guide_store = {}; const GuideStore = createStore({ addOrganization: function (org) { - console.log(org); _guide_store[org.organization_we_vote_id] = org; }, From bf7819bf66c7ea208b40c9a63843fb0d4e407401 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Wed, 24 Feb 2016 17:47:09 -0800 Subject: [PATCH 89/92] Leaving commented out code in place since we will be turning back on soon. --- src/js/routes/AddFriends.jsx | 3 +++ src/js/routes/Connect.jsx | 1 + src/js/routes/Opinions.jsx | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx index fe6ace238..1b298974a 100644 --- a/src/js/routes/AddFriends.jsx +++ b/src/js/routes/AddFriends.jsx @@ -1,4 +1,7 @@ import React, { Component } from "react"; +//import { Link } from 'react-router'; +//import { Input } from 'react-bootstrap'; +//import BottomContinueNavigation from '../components/Navigation/BottomContinueNavigation'; /* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/89479679 */ export default class AddFriends extends Component { diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 93ec5bebf..9cc4dd08b 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -1,6 +1,7 @@ import React, { Component } from "react"; import { Link } from "react-router"; import { Button } from "react-bootstrap"; +//import OrganizationsToFollowList from "../components/OrganizationsToFollowList"; /* VISUAL DESIGN HERE: https://invis.io/E45246B2C */ diff --git a/src/js/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index 8fc1e73a8..82197c385 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -74,6 +74,10 @@ export default class Opinions extends Component {

    More Opinions I Can Follow

    + {/* + + */}

    These organizations and public figures have opinions about items on your ballot. Click the "Follow" button to pay attention to them. From c9a4d8b18d13abb7db536ac9b6bd41c58c6df87a Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Wed, 24 Feb 2016 17:48:25 -0800 Subject: [PATCH 90/92] Turn off Account Settings page when NOT signed in. --- src/js/components/Header.jsx | 13 ++++++++----- src/js/components/MoreMenu.jsx | 6 +++++- src/js/routes/More/About.jsx | 7 ++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index c21d4e5da..5fff67bb7 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -94,11 +94,14 @@ export default class Header extends Component { My Ballot Location -

  • - - Account Settings - -
  • + { signedIn ? +
  • + + Account Settings + +
  • + : + } {/*
      diff --git a/src/js/components/MoreMenu.jsx b/src/js/components/MoreMenu.jsx index e93017a2d..636f1a232 100644 --- a/src/js/components/MoreMenu.jsx +++ b/src/js/components/MoreMenu.jsx @@ -43,7 +43,11 @@ export default class MoreMenu extends Component { {/*
    • Print or Email Ballot
    • */}
    • Opinions I'm Following
    • My Ballot Location
    • -
    • Account Settings
    • + {this.props.signed_in_personal ? +
    • Account Settings
    • + : + + }
    {/*
      diff --git a/src/js/routes/More/About.jsx b/src/js/routes/More/About.jsx index 86bcf0e8a..21299506c 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -33,8 +33,8 @@ export default class About extends Component { Twitter - Data
      Vote Smart - Data
      Voting Information Project, Pew Charitable Trusts - Data
      - Wikipedia - Data
      - We Vote Education - Data + We Vote Education - Data
      + Wikipedia - Data

      Special thanks to our team of volunteers. @@ -42,13 +42,14 @@ export default class About extends Component {

      Dale McGrew - Oakland, CA
      Jenifer Fernandez Ancona - Oakland, CA
      - Rob Simpson - Vienna, VA
      + Rob Simpson - Warrenton, VA
      Nicolas Fiorini - Arlington, VA
      Niko Barry - Berkeley, CA
      Joe Evans - Santa Cruz, CA
      Mary O'Connor - Sebastopol, CA
      Marissa Luna - Lansing, MI
      Aaron Borden - San Francisco, CA
      + Lisa Cho - San Francisco, CA
      Judy Johnson - Oakland, CA
      Robin Braverman - Walnut Creek, CA
      Mike McConnell - San Francisco, CA
      From 9ff1feb0359e35669b7384fd7a7b309fa07a68c0 Mon Sep 17 00:00:00 2001 From: Dale John McGrew Date: Thu, 25 Feb 2016 12:18:59 -0800 Subject: [PATCH 91/92] Made the entire CandidateItem div a link (per original designs). Changed "support/oppose" language in cases where we are using ratings. Added "source" under Vote Smart ratings. Changed "My Ballot" to "My Voter Guide", and added "demo version" per Jenifer's requests. Added "My Voter Guide" link to left menu. Added indicator in both ItemActionBar's for when the voter supports or opposes something. Did some ES lint clean up. Added _position stylesheet. Added text offering test address. --- src/js/components/Ballot/CandidateItem.jsx | 87 ++++++++++--------- src/js/components/Ballot/PositionItem.jsx | 9 +- src/js/components/Header.jsx | 10 ++- src/js/components/ItemActionBar2.jsx | 12 +-- src/js/components/ItemActionbar.jsx | 4 +- .../components/LanguageSwitchNavigation.jsx | 20 ++--- src/js/components/MoreMenu.jsx | 32 ++----- src/js/routes/Ballot/Candidate.jsx | 18 ---- src/js/routes/Ballot/EmptyBallot.jsx | 16 +++- src/js/routes/Intro/IntroContests.jsx | 2 +- src/js/routes/More/About.jsx | 3 +- src/js/routes/Settings/Location.jsx | 10 ++- src/sass/components/_candidate.scss | 15 +++- src/sass/components/_itemActionbar.scss | 6 ++ src/sass/components/_position.scss | 7 ++ src/sass/main.scss | 1 + 16 files changed, 133 insertions(+), 119 deletions(-) create mode 100644 src/sass/components/_position.scss diff --git a/src/js/components/Ballot/CandidateItem.jsx b/src/js/components/Ballot/CandidateItem.jsx index e311d5da4..9440114ab 100644 --- a/src/js/components/Ballot/CandidateItem.jsx +++ b/src/js/components/Ballot/CandidateItem.jsx @@ -76,49 +76,52 @@ export default class Candidate extends Component { we_vote_id={we_vote_id} is_starred={this.props.is_starred || false } /> -

      -
      - - {/* adding inline style to img until Rob can style... */} - { - candidate_photo_url ? - - candidate-photo : - - - - } -
      -
      -

      - - { ballot_item_display_name } - -

      -
        -
      • - - { this.state.supportCount }  - support - -
      • -
      • - - { this.state.opposeCount }  - oppose - -
      • -
      + + {/* Note: We want a click anywhere in this div to take you to the candidate page */} +
      +
      + + {/* adding inline style to img until Rob can style... */} + { + candidate_photo_url ? + + candidate-photo : + + + + } + +
      +
      +

      + { ballot_item_display_name } (more) +

      + Summary of opinions you follow: +
        +
      • + + { this.state.supportCount }  + positive + , +
      • +
      • + + { this.state.opposeCount }  + negative + +
      • +
      +
      -
      +
    ; } diff --git a/src/js/components/Ballot/PositionItem.jsx b/src/js/components/Ballot/PositionItem.jsx index 985c9169b..c7f652d55 100644 --- a/src/js/components/Ballot/PositionItem.jsx +++ b/src/js/components/Ballot/PositionItem.jsx @@ -36,17 +36,17 @@ export default class PositionItem extends Component { if (position.hasOwnProperty("is_oppose") && position.hasOwnProperty("is_support") && position.is_oppose === position.is_support){ console.log("Both positions true:", this.props.position_we_vote_id); - supportText = "Information about"; + supportText = "rates"; } else if (position.is_oppose) { - supportText = "Opposes"; + supportText = "rates"; } else if (position.is_support) { - supportText = "Supports"; + supportText = "rates"; } var dateStr = this.props.last_updated; var dateText = moment(dateStr).startOf("day").fromNow(); - return
    + return
    {/* One organization"s Position on this Candidate */}
  • @@ -69,6 +69,7 @@ export default class PositionItem extends Component {
    {position.statement_text} + (source: VoteSmart.org)
    {/* Likes coming in a later version
    diff --git a/src/js/components/Header.jsx b/src/js/components/Header.jsx index 5fff67bb7..facc23eaf 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -59,7 +59,8 @@ export default class Header extends Component {

    - My Ballot + My Voter Guide + demo version