diff --git a/.eslintignore b/.eslintignore index e69de29bb..2952f5022 100644 --- a/.eslintignore +++ b/.eslintignore @@ -0,0 +1,2 @@ +src/js/vendor/*.js +src/js/mock-data/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index e470699e7..8f524243f 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": [1], + "comma-style": [2, "last"], + "complexity": [0, 11], + "consistent-return": [1], + "consistent-this": [0, "that"], + "curly": [0, "multi-line"], + "default-case": [1], + "dot-notation": [2, {"allowKeywords": true}], + "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": [1, {"beforeColon": false, "afterColon": true}], + "quotes": [1, "double", "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": [1], + "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": [1], + "no-loop-func": [1], + "no-mixed-requires": [0, false], + "no-mixed-spaces-and-tabs": [2, false], + "no-multi-spaces": [1], + "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": [1], + "no-path-concat": [0], + "no-plusplus": [0], + "no-process-env": [0], + "no-process-exit": [2], + "no-proto": [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": [1], + "no-shadow": [1], + "no-shadow-restricted-names": [2], + "no-spaced-func": [1], + "no-sparse-arrays": [2], + "no-sync": [0], + "no-ternary": [0], + "no-throw-literal": [2], + "no-trailing-spaces": [1], + "no-undef": [1], + "no-undef-init": [2], + "no-undefined": [0], + "no-underscore-dangle": [0], + "no-unreachable": [2], + "no-unused-expressions": [1], + "no-unused-vars": [1], + "no-use-before-define": [1], + "no-void": [0], + "no-warning-comments": [0, {"terms": ["todo", "fixme", "xxx"], "location": "start"}], + "no-with": [2], + "no-extra-parens": [1], + "one-var": [1, "never"], + "operator-assignment": [0, "always"], + "operator-linebreak": [1, "after"], + "padded-blocks": [0], + "quote-props": [0], + "radix": [0], + "semi": [1], + "semi-spacing": [2, {"before": false, "after": true}], + "sort-vars": [0], + "space-after-keywords": [2, "always"], + "space-before-function-paren": [1, {"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": [1], + "space-return-throw-case": [2], + "space-unary-ops": [0, {"words": true, "nonwords": false}], + "spaced-line-comment": [0, "always"], + "strict": [0, "never"], + "use-isnan": [2], + "valid-jsdoc": [0], + "valid-typeof": [2], + "vars-on-top": [0], + "wrap-iife": [2], + "wrap-regex": [1], + "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/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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 136a1fbfd..f44fad658 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,49 +1,168 @@ -# Contributing + + +## Contents -This outlines the proper ways to contribute to the project. +- [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. + (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. - -Here is an outline of doing a pull request: - -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. -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. + 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/README.md b/README.md index d3d2bb488..0a7930f7b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -# README for We Vote WebApp +# We Vote WebApp -[![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) +[![Build Status](https://travis-ci.org/wevote/WebApp.svg?branch=develop)](https://travis-ci.org/wevote/WebApp) - npm install - npm start +![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 @@ -13,10 +11,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/ -## 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) - +## 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") @@ -48,8 +45,15 @@ 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. +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. -## Clone https://github.com/wevote/WebApp +## Set up your environment Create a place to put all of the code from Github: @@ -59,7 +63,6 @@ Retrieve “WebApp” into that folder, so your code ends up here: /Users//MyProjects/WebApp - ## Install and start web application (WebAppEnv) $ cd /Users//MyProjects/WebApp @@ -71,9 +74,43 @@ 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 +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 [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. + Welcome aboard!! 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/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 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..6041edde2 100644 --- a/package.json +++ b/package.json @@ -14,25 +14,31 @@ }, "dependencies": { "keymirror": "^0.1.1", + "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" + "superagent": "^1.5.0", + "underscore": "^1.8.3" }, "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 +50,11 @@ "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", + "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", @@ -61,26 +68,22 @@ "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 && jscs src", - "test": "eslint src && jest", - "start": "gulp" + "lint": "eslint --format stylish --ext .jsx --ext .js src/js", + "test": "npm run lint", + "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\" ./" }, - "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" - ] - } + "pre-commit": [ + "test", + "lint" + ] } diff --git a/server.js b/server.js index 332ea630d..f656b02f2 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ // start the express server -const express = require('express'); +const express = require("express"); const app = express(); const port = 3003; @@ -8,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/.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/Application.jsx b/src/js/Application.jsx index f3d125227..a1808e659 100644 --- a/src/js/Application.jsx +++ b/src/js/Application.jsx @@ -1,39 +1,59 @@ 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"; +import VoterStore from "./stores/VoterStore"; export default class Application extends Component { static propTypes = { - children: PropTypes.object, - voter_object: PropTypes.object + children: PropTypes.element, + route: PropTypes.object, + location: 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}); + componentDidMount () { + // 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 { voter_object } = this.state; + render () { + var { location: { pathname }} = this.props; + var { voter } = this.state; + // console.log("In Application.jsx render, voter: ", voter) - return ( -
+ return
-
+
@@ -43,23 +63,15 @@ export default class Application extends Component {
-
- { - voter_object ? -
- -
- : - - } +
+ { voter.signed_in_personal ? : }
-
+
{ this.props.children }
- -
- ); + +
; } } diff --git a/src/js/Root.jsx b/src/js/Root.jsx index 11dc5c60a..1f557bd05 100644 --- a/src/js/Root.jsx +++ b/src/js/Root.jsx @@ -1,129 +1,109 @@ -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"; +import EmptyBallot from "./routes/Ballot/EmptyBallot"; /* Ballot Off-shoot Pages */ -import Opinions from './routes/Opinions'; +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'; -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 AddFriend from './routes/AddFriend'; - - -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 */} + + + + + + + {/* Voter Guide Pages */} + + + { firstVisit ? : } + + + + + + + + {/* + + + + + + + */} + + + + + + + + // Any route that is not found -> @return NotFound component + + ; + + +export default routes; diff --git a/src/js/actions/BallotActions.js b/src/js/actions/BallotActions.js index d93b6a450..29b8f02e0 100644 --- a/src/js/actions/BallotActions.js +++ b/src/js/actions/BallotActions.js @@ -1,10 +1,45 @@ -'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 module.exports = { + + // positionsRetrieved: function (we_vote_id, payload) { + // AppDispatcher.dispatch({ + // actionType: BallotConstants.POSITIONS_RETRIEVED, + // payload: payload, + // we_vote_id: we_vote_id + // }); + // }, + // + // starStatusRetrieved: function (we_vote_id, payload) { + // AppDispatcher.dispatch({ + // actionType: BallotConstants.STAR_STATUS_RETRIEVED, + // payload: payload, + // we_vote_id: we_vote_id + // }); + // }, + + candidateItemRetrieved: function (we_vote_id, parameter, parameter_alias, payload) { // CANDIDATE_DETAIL_RETRIEVED + AppDispatcher.dispatch({ + actionType: BallotConstants.CANDIDATE_DETAIL_RETRIEVED, + payload: payload, + parameter: parameter, + parameter_alias: parameter_alias, + we_vote_id: we_vote_id + }); + }, + + candidateRetrieved: function (payload) { // CANDIDATE_RETRIEVED + 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/actions/FacebookActionCreators.js b/src/js/actions/FacebookActionCreators.js index 2365ba0e0..972e4364b 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,31 +57,38 @@ const FacebookActionCreators = { FacebookDispatcher.dispatch({ actionType: FacebookConstants.FACEBOOK_LOGGED_OUT, data: response - }) - }) + }); + }); }, + // 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 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/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/actions/PositionActions.js b/src/js/actions/PositionActions.js new file mode 100644 index 000000000..6c3af4f32 --- /dev/null +++ b/src/js/actions/PositionActions.js @@ -0,0 +1,15 @@ +"use strict"; +var AppDispatcher = require("../dispatcher/AppDispatcher"); +var PositionConstants = require("../constants/PositionConstants"); + +module.exports = { + + positionRetrieved: function (payload) { + AppDispatcher.dispatch({ + actionType: PositionConstants.POSITION_RETRIEVED, + payload: payload, + we_vote_id: payload.position_we_vote_id + }); + }, + +}; diff --git a/src/js/actions/VoterActions.js b/src/js/actions/VoterActions.js index 414e4b031..f8a9ce7d6 100644 --- a/src/js/actions/VoterActions.js +++ b/src/js/actions/VoterActions.js @@ -1,7 +1,9 @@ -'use strict'; -var AppDispatcher = require('../dispatcher/AppDispatcher'); -var VoterConstants = require('../constants/VoterConstants'); +"use strict"; +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/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/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 3750e0d20..9440114ab 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 = { @@ -30,11 +30,12 @@ export default class Candidate 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 () { @@ -44,7 +45,7 @@ export default class Candidate extends Component { }); } - render() { + render () { let { we_vote_id, ballot_item_display_name, @@ -59,68 +60,69 @@ 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
-
-
- - {/* 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/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 new file mode 100644 index 000000000..c7f652d55 --- /dev/null +++ b/src/js/components/Ballot/PositionItem.jsx @@ -0,0 +1,81 @@ +import React, { Component, PropTypes } from "react"; +import { Link } from "react-router"; +import PositionStore from "../../stores/PositionStore"; +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) { + super(props); + this.state = { position: {} }; + } + + componentDidMount () { + this.changeListener = this._onChange.bind(this); + PositionStore.retrievePositionByWeVoteId(this.props.position_we_vote_id); + PositionStore.addChangeListener(this.changeListener); + } + + componentWillUnmount () { + PositionStore.removeChangeListener(this.changeListener); + } + + _onChange () { + 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; + + 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 = "rates"; + } else if (position.is_oppose) { + supportText = "rates"; + } else if (position.is_support) { + supportText = "rates"; + } + + var dateStr = this.props.last_updated; + var dateText = moment(dateStr).startOf("day").fromNow(); + + return
+ {/* One organization"s Position on this Candidate */} +
  • +
    +
    + + { this.props.speaker_image_url_https + ? + : } + +
    +
    +

    + { this.props.speaker_display_name }
    +

    +

    {supportText} {this.props.candidate_display_name} { dateText }

    +
    +
    +
    + {position.statement_text} + (source: VoteSmart.org) +
    + {/* Likes coming in a later version +
    + 23 Likes
    + */} +
  • +
    ; + } +} diff --git a/src/js/components/Ballot/PositionList.jsx b/src/js/components/Ballot/PositionList.jsx new file mode 100644 index 000000000..d0e5accfb --- /dev/null +++ b/src/js/components/Ballot/PositionList.jsx @@ -0,0 +1,47 @@ +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: [] + }; + } + + componentDidMount (){ + BallotStore.fetchCandidatePositions(this.props.we_vote_id); + this.changeListener = this._onChange.bind(this); + BallotStore.addChangeListener(this.changeListener); + } + + componentWillUnmount (){ + BallotStore.removeChangeListener(this.changeListener); + } + + _onChange (){ + this.setState({ position_list: BallotStore.getCandidateByWeVoteId(this.props.we_vote_id).position_list }); + } + + render () { + if (!this.state.position_list){ + return
    ; + } + return
      + { this.state.position_list.map( item => + ) + } +
    ; + } +} diff --git a/src/js/components/Facebook/FacebookSignIn.jsx b/src/js/components/Facebook/FacebookSignIn.jsx index 0afb782f8..b13c6f3d2 100644 --- a/src/js/components/Facebook/FacebookSignIn.jsx +++ b/src/js/components/Facebook/FacebookSignIn.jsx @@ -1,25 +1,21 @@ -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"; +import VoterActions from "../../actions/VoterActions"; 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(); // We call FacebookActionCreators.connectWithFacebook() within login() + } } export default FacebookSignIn; diff --git a/src/js/components/Facebook/Main.js b/src/js/components/Facebook/Main.js index 89e0383df..ee112c251 100644 --- a/src/js/components/Facebook/Main.js +++ b/src/js/components/Facebook/Main.js @@ -1,52 +1,52 @@ -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()); + this.changeListener = this._onFacebookChange.bind(this); + FacebookStore.addChangeListener(this.changeListener); } - componentWillUnmount() { - FacebookStore.removeChangeListener(this._onFacebookChange); + componentWillUnmount () { + FacebookStore.removeChangeListener(this.changeListener) } - _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/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/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/Header.jsx b/src/js/components/Header.jsx index 2ebdc1659..facc23eaf 100644 --- a/src/js/components/Header.jsx +++ b/src/js/components/Header.jsx @@ -1,31 +1,149 @@ +const web_app_config = require("../config"); 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) { + 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 = { + visible: false + }; + } + + componentDidMount () { + VoterStore.getLocation( (err, location) => { + if (err) console.error(err); + + this.setState({ location }); + + }); + } + + 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 = 0.2; + } + } + + hide () { + this.setState({visible: false}); + document.querySelector(".container-main").style.opacity = 1; } render () { - return ( -
    + var { location, visible } = this.state; + var { voter_photo_url: url, signed_in_personal: signedIn } = this.props; + // var image; + // + // if (url) + // image = ; + + const header = +

    - - - - My Ballot + + + My Voter Guide + demo version

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

    +
    + } +
      +
    • + + My Voter Guide + +
    • +
    • + + Opinions I'm Following + +
    • +
    • + + My Ballot Location + +
    • + { signedIn ? +
    • + + Account Settings + +
    • + : + } + {/*
    • Print or Email Ballot
    • */} +
    + {/* +
      +
    • + +
    • +
    + */} +

    +
      +
    • + + About We Vote + +
    • + + {/*
    • Terms & Policies
    • */} + +
    • + + Admin + +
    • + + {/* To be implemented in a coming release + { signedIn ? +
    • + + Sign Out + +
    • : } + */} +
    +
    +
    ; + + return header; } } diff --git a/src/js/components/ItemActionBar2.jsx b/src/js/components/ItemActionBar2.jsx index fecb78567..11c130f37 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 () { @@ -60,8 +61,8 @@ export default class ItemActionBar2 extends Component {
    {this.state.is_support ? - - {this.state.supportCount} support + + {this.state.supportCount} positive @@ -69,7 +70,7 @@ export default class ItemActionBar2 extends Component { : - {this.state.supportCount} support + {this.state.supportCount} positive @@ -77,8 +78,8 @@ export default class ItemActionBar2 extends Component { } {this.state.is_oppose ? - - {this.state.opposeCount} oppose + + {this.state.opposeCount} negative @@ -86,17 +87,19 @@ export default class ItemActionBar2 extends Component { : - {this.state.opposeCount} oppose + {this.state.opposeCount} negative } + {/* Share coming in a later version  Share + */}
    ); } diff --git a/src/js/components/ItemActionbar.jsx b/src/js/components/ItemActionbar.jsx index 03c2569c1..78f24531c 100644 --- a/src/js/components/ItemActionbar.jsx +++ b/src/js/components/ItemActionbar.jsx @@ -19,11 +19,12 @@ export default class ItemActionbar 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 () { @@ -50,48 +51,45 @@ 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 + + + + Support - } - {this.state.is_oppose ? - - - - - Oppose - - - : - - - - - Oppose + + + + + Oppose - } + + {/* Share coming in a later version - + + + +  Share -  Share -
    - ); + */} +
    ; + + return itemActionBar; } } diff --git a/src/js/components/LanguageSwitchNavigation.jsx b/src/js/components/LanguageSwitchNavigation.jsx index 179450540..2f07276d9 100644 --- a/src/js/components/LanguageSwitchNavigation.jsx +++ b/src/js/components/LanguageSwitchNavigation.jsx @@ -5,19 +5,17 @@ export default class LanguageSwitchNavigation extends Component { static propTypes = { language_selected: PropTypes.string }; - constructor(props) { + constructor (props) { super(props); } - render() { - return ( - - {(this.props.language_selected == "chinese") ? : 中国
    } - {(this.props.language_selected == null) ? : English
    } - {(this.props.language_selected == "spanish") ? : Español
    } - {(this.props.language_selected == "tagalog") ? : Tagalog
    } - {(this.props.language_selected == "vietnamese") ? : tiếng Việt
    } -
    - ); + render () { + return + {this.props.language_selected === "chinese" ? : 中国
    } + {this.props.language_selected === null ? : English
    } + {this.props.language_selected === "spanish" ? : Español
    } + {this.props.language_selected === "tagalog" ? : Tagalog
    } + {this.props.language_selected === "vietnamese" ? : tiếng Việt
    } +
    ; } } 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/MoreMenu.jsx b/src/js/components/MoreMenu.jsx index db38c6c81..86a140517 100644 --- a/src/js/components/MoreMenu.jsx +++ b/src/js/components/MoreMenu.jsx @@ -1,38 +1,26 @@ const web_app_config = require("../config"); import React, { PropTypes, Component } from "react"; import { Link } from "react-router"; -import LanguageSwitchNavigation from "../components/LanguageSwitchNavigation"; -import VoterStore from "../stores/VoterStore"; +//import LanguageSwitchNavigation from "../components/LanguageSwitchNavigation"; 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) { + constructor (props) { super(props); } - render() { - var voter_image = ""; - if (this.props.voter_photo_url) { - voter_image = - } - - return ( -
    + render () { + return
    {this.props.signed_in_personal ? - : - + :
    • Sign In
    @@ -40,10 +28,13 @@ export default class MoreMenu extends Component {
    }
      - {/*
    • Print or Email Ballot
    • */} +
    • My Voter Guide
    • Opinions I'm Following
    • My Ballot Location
    • -
    • Account Settings
    • + {this.props.signed_in_personal ? +
    • Account Settings
    • : + } + {/*
    • Print or Email Ballot
    • */}
    {/*
      @@ -56,16 +47,18 @@ 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/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/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/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/components/StarAction.jsx b/src/js/components/StarAction.jsx index ed8bf03cd..0a57e4780 100644 --- a/src/js/components/StarAction.jsx +++ b/src/js/components/StarAction.jsx @@ -30,13 +30,12 @@ export default class StarAction extends Component { } componentDidMount () { - //console.log("DidMount star action, is_starred: " + this.state.is_starred); - BallotStore.addChangeListener(this._onChange.bind(this)); + this.changeListener = this._onChange.bind(this); + BallotStore.addChangeListener(this.changeListener); } - componentWillUnmount() { - //console.log("WillUnmount star action, is_starred: " + this.state.is_starred); - BallotStore.removeChangeListener(this._onChange.bind(this)); + componentWillUnmount () { + BallotStore.removeChangeListener(this.changeListener); } _onChange () { @@ -45,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/js/components/VoterGuide/GuideList.jsx b/src/js/components/VoterGuide/GuideList.jsx new file mode 100644 index 000000000..cb39bb795 --- /dev/null +++ b/src/js/components/VoterGuide/GuideList.jsx @@ -0,0 +1,114 @@ +import React, { Component, PropTypes } from "react"; +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 { + + static propTypes = { + organizations: PropTypes.array + }; + + 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() }; + + } + + 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) { + + 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 () { + + let orgs = this.state.orgList.map( (org, i) => { + + var { + organization_we_vote_id: id, + voter_guide_display_name: displayName, + voter_guide_image_url: imageUrl, + twitter_followers_count: followers + } = org; + + // Key can be id once issue #85 is resolved on server + // https://github.com/wevote/WeVoteServer/issues/85 + const key = id + "-" + i; + + const organization = + + + + ; + + return organization; + + }); + + 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..96ceb585b --- /dev/null +++ b/src/js/components/VoterGuide/Organization.jsx @@ -0,0 +1,48 @@ +import React, { Component, PropTypes } from "react"; + +function numberWithCommas (num) { + var parts = num.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} + +export default class Organization extends Component { + static propTypes = { + id: PropTypes.string, + key: PropTypes.string, + imageUrl: PropTypes.string, + displayName: PropTypes.string, + followers: PropTypes.number, + children: PropTypes.array + }; + + constructor (props) { + super(props); + } + + render () { + + const { + displayName, + imageUrl, + followers, + } = this.props; + + const org = +
    +
    + {displayName +
    +

    {displayName}

    +

    {numberWithCommas(followers)} Twitter Followers

    +

    + {this.props.children} +

    +
    +
    +
    ; + + return org; + } + +} diff --git a/src/js/components/VoterGuide/VoterGuideItem.jsx b/src/js/components/VoterGuide/VoterGuideItem.jsx index 6e75e4a74..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 @@ -28,11 +35,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 () { @@ -45,13 +53,23 @@ 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 ( -
    +
    -
    -   +
    + +   { this.props.voter_guide_display_name } + {twitterFollowers}
    diff --git a/src/js/config.js b/src/js/config.js index 97660aeaa..24851ad6c 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -1,9 +1,10 @@ -// 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 +13,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..2a827c0a3 100644 --- a/src/js/constants/BallotConstants.js +++ b/src/js/constants/BallotConstants.js @@ -1,9 +1,11 @@ // Ballot Constants -module.exports = require('keymirror')({ +module.exports = require("keymirror")({ VOTER_SUPPORTING_SAVE: null, VOTER_STOP_SUPPORTING_SAVE: null, VOTER_OPPOSING_SAVE: null, VOTER_STOP_OPPOSING_SAVE: null, VOTER_STAR_ON_SAVE: null, - VOTER_STAR_OFF_SAVE: null + VOTER_STAR_OFF_SAVE: null, + CANDIDATE_RETRIEVED: null, + CANDIDATE_DETAIL_RETRIEVED: 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..cccffa26b 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, @@ -7,8 +7,9 @@ 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, -} +}; -module.exports = keyMirror(FacebookConstants) \ No newline at end of file +module.exports = keyMirror(FacebookConstants); diff --git a/src/js/constants/GuideConstants.js b/src/js/constants/GuideConstants.js new file mode 100644 index 000000000..4a0f9f337 --- /dev/null +++ b/src/js/constants/GuideConstants.js @@ -0,0 +1,4 @@ +module.exports = require("keymirror")({ + ORG_IGNORE: null, + ORG_FOLLOW: null +}); diff --git a/src/js/constants/PositionConstants.js b/src/js/constants/PositionConstants.js new file mode 100644 index 000000000..be517d31d --- /dev/null +++ b/src/js/constants/PositionConstants.js @@ -0,0 +1,3 @@ +module.exports = require("keymirror")({ + POSITION_RETRIEVED: null +}); diff --git a/src/js/constants/VoterConstants.js b/src/js/constants/VoterConstants.js index c057319fc..79b40e67f 100644 --- a/src/js/constants/VoterConstants.js +++ b/src/js/constants/VoterConstants.js @@ -1,6 +1,7 @@ -const keyMirror = require('keymirror'); +const keyMirror = require("keymirror"); module.exports = keyMirror({ + VOTER_CHANGE_LOCATION: null, VOTER_LOCATION_RETRIEVE: null, VOTER_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 32c64cd60..5b211c9e2 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,26 +1,96 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { createHistory } from 'history'; -import Root from './Root'; +import React from "react"; +import Router from "react-router"; +import ReactDOM from "react-dom"; +import { createHistory } from "history"; -import VoterStore from './stores/VoterStore'; - -console.log('Entering WebApp/src/js/index.js'); +import VoterStore from "./stores/VoterStore"; +import routes from "./Root"; // polyfill if (!Object.assign) Object.assign = React.__spread; -const firstVisit = VoterStore.voter_device_id ? false : true; +// 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.getVoterObject( (err, voter) => { + if (err) handleVoterError(err); + + renderApp(true, voter); + + }); + + }); + + }); + + }); -//console.log("index.js: About to initialize VoterStore"); -//VoterStore.initialize((voter_object) => { -// ReactDOM.render( -// , -// document.getElementById('app') -// ); -//}); + } -ReactDOM.render( - , - document.getElementById('app') -); +}()); diff --git a/src/js/routes/Activity.jsx b/src/js/routes/Activity.jsx index 6007e95d3..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,10 +14,12 @@ export default class Activity extends Component { } render() { - return ( + return (
    -
    - Activity Feed Coming Soon +
    +

    Activity Feed

    +

    Coming Soon

    +

    See the latest endorsements and news.

    ); diff --git a/src/js/routes/AddFriend.jsx b/src/js/routes/AddFriend.jsx deleted file mode 100644 index f6bcc8490..000000000 --- a/src/js/routes/AddFriend.jsx +++ /dev/null @@ -1,35 +0,0 @@ -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 { - constructor(props) { - super(props); - } - - static getProps() { - return {}; - } - - render() { - return ( -
    -
    -

    Add Friends

    -
    -
    -
    - - These friends will see what you support, oppose, and which opinions you follow. - We never sell email addresses.
    -
    -
    - -
    - ); - } -} diff --git a/src/js/routes/AddFriends.jsx b/src/js/routes/AddFriends.jsx new file mode 100644 index 000000000..1b298974a --- /dev/null +++ b/src/js/routes/AddFriends.jsx @@ -0,0 +1,40 @@ +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 { + constructor (props) { + super(props); + } + + static getProps () { + return {}; + } + + render () { + 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.
    +
    + */} +
    + {/* Still to be implemented + + */} +
    ; + } +} diff --git a/src/js/routes/Ballot/Ballot.jsx b/src/js/routes/Ballot/Ballot.jsx index 58bdad8f0..e61948389 100644 --- a/src/js/routes/Ballot/Ballot.jsx +++ b/src/js/routes/Ballot/Ballot.jsx @@ -1,40 +1,43 @@ -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'; export default class Ballot extends Component { static propTypes = { + history: PropTypes.object, children: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); this.state = {}; } componentDidMount () { - BallotStore.initialize( (ballot_list) => this.setState({ ballot_list }) ); + 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 ( + const ballot =
    - { - ballot_list ? ballot_list - .map( item => - - ) : ( -
    - -

    Loading ... One Moment

    -
    - ) - } -
    - ); + { ballot_list ? ballot_list.map( item => + + ) : LoadingWheel } +
    ; + + return ballot; } -}; +} diff --git a/src/js/routes/Ballot/Candidate.jsx b/src/js/routes/Ballot/Candidate.jsx index d404adc77..9843a6f7f 100644 --- a/src/js/routes/Ballot/Candidate.jsx +++ b/src/js/routes/Ballot/Candidate.jsx @@ -1,109 +1,87 @@ -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 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 = { - //history: PropTypes.func.isRequired, - params: PropTypes.object.isRequired, + params: PropTypes.object.isRequired }; constructor(props) { super(props); + this.state = { candidate: {} }; } - render() { - var candidate = BallotStore.getCandidateByWeVoteId(`${this.props.params.we_vote_id}`); - - // no candidate exists... go to ballot - if (Object.keys(candidate).length === 0) - this.props.history.replace('/ballot'); + componentWillUnmount() { + BallotStore.removeChangeListener(this.changeListener); + } - var support_item; - if (this.props.support_on) { - support_item = 7 ; - } else { - support_item = 7 ; + 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) { + this.setState({ candidate: candidate }); } + } - var oppose_item; - if (this.props.oppose_on) { - oppose_item = 3 ; - } else { - oppose_item = 3 ; - } + _onChange(){ + this.setState({ candidate: BallotStore.getCandidateByWeVoteId(this.props.params.we_vote_id) }); + } - return ( -
    - {/* -
    -
    - - < Back to My Ballot - -
    -
    - - - - More Opinions - -
    -
    - */} + render() { + var candidate = this.state.candidate; + var we_vote_id = this.props.params.we_vote_id; + if (!candidate || !candidate.we_vote_id){ + return (
    ); + }; - +
    + {candidate.hasOwnProperty("is_starred") ? + -
    + : +
    } + +
    + className="col-xs-6" + style={candidate.candidate_photo_url ? {} : {height:"95px"}}> { candidate.candidate_photo_url ? - candidate-photo : - - }
    -
    +

    - - { candidate.ballot_item_display_name } - + { 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 */} -
    -
    -
    -
    Running for US House - District 12
    - +

    Running for { candidate.office_display_name }

    -
    + +
    + {/* Post privately box */} + {/*
    • @@ -112,41 +90,14 @@ export default class Candidate extends Component {
    -
      -
    • -
      -
      - - - -
      -
      -

      - - 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
      -
    • -
    + */} + { + + }
    + ); } diff --git a/src/js/routes/Ballot/EmptyBallot.jsx b/src/js/routes/Ballot/EmptyBallot.jsx new file mode 100644 index 000000000..bde4b324e --- /dev/null +++ b/src/js/routes/Ballot/EmptyBallot.jsx @@ -0,0 +1,35 @@ +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 ( +
    +
    +

    + Your Ballot Is Not Ready Yet +

    +
    + Our data providers don't have ballot data for your address yet. + Please check back 1-2 weeks before your election day. Thank you for your patience. +
    +
    +
    +
    + See We Vote in action! Copy this test address into "My Ballot Location": +
    +

    + 2208 Ebb Tide Rd, Virginia Beach, VA 23451 +

    +
    +
    + ); + } +} diff --git a/src/js/routes/Connect.jsx b/src/js/routes/Connect.jsx index 988f4eeff..9cc4dd08b 100644 --- a/src/js/routes/Connect.jsx +++ b/src/js/routes/Connect.jsx @@ -1,47 +1,51 @@ -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 */} +/* VISUAL DESIGN HERE: https://invis.io/E45246B2C */ export default class Connect extends Component { - static propTypes = { + static propTypes = { - }; + }; - constructor(props) { + constructor (props) { super(props); } - static getProps() { + static getProps () { return {}; } - render() { - var floatRight = { - float: 'right' - }; - return ( -
    -
    -

    Add Friends

    - - - -

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

    - -

    Follow More Opinions

    - - - -
    -
    + render () { + var floatRight = { + float: "right" + }; + 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/Guide/PositionList.jsx b/src/js/routes/Guide/PositionList.jsx new file mode 100755 index 000000000..58942ef3a --- /dev/null +++ b/src/js/routes/Guide/PositionList.jsx @@ -0,0 +1,66 @@ +import React, { Component, PropTypes } from "react"; +import { Button } from "react-bootstrap"; +import { Link } from "react-router"; + +import StarAction from "../../components/StarAction"; + +/* VISUAL DESIGN HERE: https://projects.invisionapp.com/share/2R41VR3XW#/screens/94226088 */ + +export default class GuidePositionList extends Component { + static propTypes = { + //oppose_on: PropTypes.boolean, + //params: PropTypes.object.isRequired, + //support_on: PropTypes.boolean + }; + + constructor (props) { + super (props); + } + + 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
      + + 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..fa0859bb3 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:

    @@ -42,7 +42,7 @@ export default class IntroBallotContests extends Component { @@ -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 377f38d6c..190c654fd 100644 --- a/src/js/routes/More/About.jsx +++ b/src/js/routes/More/About.jsx @@ -1,44 +1,91 @@ -import React, { Component } from 'react'; -import { Button } from 'react-bootstrap'; -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) { - super(props); - } + constructor (props) { + super(props); + } - static getProps() { - return {}; - } + static getProps () { + return {}; + } - render() { - return ( -
    -
    -

    About We Vote

    + render () { + return
    +
    +

    About We Vote

    +

    + We Vote USA is nonprofit and nonpartisan. For more information, please visit www.WeVoteUSA.org. + This is a demonstration version and has not been launched to the general public yet. +

    -

    We Vote USA is a nonprofit, nonpartisan, volunteer-driven movement, dedicated to helping you make voting decisions. - We are volunteer designers, engineers, thought leaders, political junkies, and good citizens. - We contribute our time and passion because we feel political decisions should be made following - clear-headed conversations as opposed to through epic media battles, cast in terms of good versus evil. -

    +

    Acknowledgements

    - - -    - - -    - - - - - +

    We are grateful for these organizations that are critical to our work.
    +
    + Ballotpedia - Data
    + CivicMakers - Event Collaborations
    + Code for San Francisco & Code for America - Our Home for Volunteer Work
    + DLA Piper - Legal
    + Facebook - Data
    + Google Civic - Data
    + Twitter - Data
    + Vote Smart - Data
    + Voting Information Project, Pew Charitable Trusts - Data
    + We Vote Education - Data
    + Wikipedia - Data +

    +

    + Special thanks to our team of volunteers. + You are the best! (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 - 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
    + Dan Ancona - Oakland, CA
    + Zak Zaidman - Ojai, CA
    + Debra Cleaver - San Francisco, CA
    + William Winters - Oakland, CA
    + Andrea Moed - San Francisco, CA
    + Anat Shenker-Osorio - Oakland, CA
    + Kad Smith - Berkeley, CA
    + Courtney Gonzales - Benicia, CA
    + Jenna Haywood - Berkeley, CA
    + Tom Furlong - Menlo Park, CA
    + Susan Clark - Oakland, CA
    + 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
    + Miguel Elasmar - Sarasota, FL
    + Nicole Shanahan - Palo Alto, CA
    + Steve Whetstone - San Francisco, CA
    + Marcus Busby - San Francisco, CA
    + lulu - New York, NY
    + Chris Griffith - Santa Cruz, CA
    + Nathan Stankowski - San Rafael, CA
    + Sean McMahon - Redwood City, CA
    + Scott Wasserman - Philadelphia, PA
    + Adrienne Yang - Oakland, CA
    + Mark Rosenthal - Oakland, CA
    +

    -
    -
    - ); - } +
    +
    ; + } } diff --git a/src/js/routes/More/EmailBallot.jsx b/src/js/routes/More/EmailBallot.jsx index d2bf614c0..f4ca423e1 100755 --- a/src/js/routes/More/EmailBallot.jsx +++ b/src/js/routes/More/EmailBallot.jsx @@ -1,47 +1,55 @@ 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 ( -
    -
    -

    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
    -
    - -
    - -
    -
    -
    -
    -
    - ); + 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. + See privacy policy. +
    + + +
    +
    +
    + OR +
    +
    + +
    + + +
    +
    +
    +
    +
    ; + + return emailBallot; } } diff --git a/src/js/routes/More/OpinionsFollowed.jsx b/src/js/routes/More/OpinionsFollowed.jsx index bc2562200..88101a2ed 100755 --- a/src/js/routes/More/OpinionsFollowed.jsx +++ b/src/js/routes/More/OpinionsFollowed.jsx @@ -1,47 +1,50 @@ 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/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( 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() { - return ( -
    -
    -

    Opinions I Follow

    + render () { + return
    +
    +

    Opinions I'm Following

    + {/*
    - + */}
    { this.state.voter_guide_followed_list ? this.state.voter_guide_followed_list.map( item => - ) : (
    - -

    Loading ... One Moment

    -
    - ) + ) : LoadingWheel + }
    -
    - ); +
    ; } } 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 958702840..99c004c2a 100755 --- a/src/js/routes/More/SignIn.jsx +++ b/src/js/routes/More/SignIn.jsx @@ -1,61 +1,71 @@ -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'; - -{/* VISUAL DESIGN: tbd */} +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"; 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'); - VoterStore.addChangeListener(this._onVoterStoreChange.bind(this)); + FacebookActionCreators.initFacebook(); + 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.voterListener); } - componentWillUnmount() { - FacebookStore.removeChangeListener(this._onFacebookChange); + componentWillUnmount () { + FacebookStore.removeChangeListener(this.changeListener); - console.log('SignIn componentWillUnmount VoterStore.removeChangeListener'); - VoterStore.removeChangeListener(this._onVoterStoreChange.bind(this)); + // console.log("SignIn componentWillUnmount VoterStore.removeChangeListener"); + VoterStore.removeChangeListener(this.voterListener); } _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 +80,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 +104,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/routes/Opinions.jsx b/src/js/routes/Opinions.jsx index a5336db4d..82197c385 100755 --- a/src/js/routes/Opinions.jsx +++ b/src/js/routes/Opinions.jsx @@ -1,49 +1,91 @@ -import React, {Component, PropTypes } from 'react'; -import HeaderBackNavigation from '../Components/Navigation/HeaderBackNavigation'; +import React, {Component, PropTypes } from "react"; +import { $ajax } from "../utils/service"; -import VoterGuideStore from '../stores/VoterGuideStore'; -import VoterGuideItem from '../components/VoterGuide/VoterGuideItem'; +import LoadingWheel from "../components/LoadingWheel"; -{/* 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 = { + history: PropTypes.object, children: PropTypes.object }; - constructor(props) { + constructor (props) { super(props); - this.state = {}; + this.state = { loading: true, error: false }; + this.electionId = BallotStore.getGoogleCivicElectionId(); } componentDidMount () { - VoterGuideStore.initialize( voter_guide_list => this.setState({ voter_guide_list })); + + if (! this.electionId ) + this.props.history.push("/ballot"); + + 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 }); + } + }); + } - 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. - -
    - { - this.state.voter_guide_list ? - this.state.voter_guide_list.map( item => - - ) : (
    - -

    Loading ... One Moment

    -
    ) - } -
    -
    -
    - ); + render () { + const EMPTY_TEXT = "You do not have a ballot yet!"; + + const { loading, error } = this.state; + const { guideList, electionId } = this; + + let guides; + + if ( !electionId ) + guides = EMPTY_TEXT; + + else + if (loading) + guides = + LoadingWheel; + + else if (error) + guides = "Error loading your organizations"; + + else if (guideList instanceof Array && guideList.length > 0) + guides = ; + + + else + 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. +

    + {guides} +
    +
    ; + + return content; } } diff --git a/src/js/routes/Requests.jsx b/src/js/routes/Requests.jsx index ec9b02234..e2a3d6535 100644 --- a/src/js/routes/Requests.jsx +++ b/src/js/routes/Requests.jsx @@ -13,28 +13,33 @@ 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.

    + {/*

    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/routes/Settings/Location.jsx b/src/js/routes/Settings/Location.jsx index ab89ee07a..af2d89d96 100644 --- a/src/js/routes/Settings/Location.jsx +++ b/src/js/routes/Settings/Location.jsx @@ -1,30 +1,83 @@ -import React, { Component } from 'react'; -import { Button, ButtonToolbar } from 'react-bootstrap'; -import HeaderBackNavigation from '../../components/Navigation/HeaderBackNavigation'; +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) { - 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( (err, location) => { + this.setState({ location }); + }); + } + + updateLocation (e) { + this.setState({ + location: e.target.value + }); + } + + saveLocation () { + var { location } = this.state; + 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) =>{ + + }); + } + + 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. + + + +
    +
    +
    + See We Vote in action! Copy and paste this address above: +
    +

    + 2208 Ebb Tide Rd, Virginia Beach, VA 23451 +

    + +
    + + + + +
    +
    +
    +
    ; + } } diff --git a/src/js/stores/BallotStore.js b/src/js/stores/BallotStore.js index 871b2879f..184a33f8e 100644 --- a/src/js/stores/BallotStore.js +++ b/src/js/stores/BallotStore.js @@ -1,157 +1,178 @@ -import service from '../utils/service'; -import { createStore } from '../utils/createStore'; -import { shallowClone } from '../utils/object-utils'; - -const AppDispatcher = require('../dispatcher/AppDispatcher'); -const BallotConstants = require('../constants/BallotConstants'); +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"); let _ballot_store = {}; let _ballot_order_ids = []; let _google_civic_election_id = null; -const MEASURE = 'MEASURE'; +const MEASURE = "MEASURE"; -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 defaultSuccess (res) { + // console.warn(res); } 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: { + voterBallotItemsRetrieveFromGoogleCivic: function (text_for_map_search, success ) { + return get({ + 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: office_we_vote_id }, + success: success || defaultSuccess + }); + }, + + candidateRetrieve: function (we_vote_id, success ) { + return get({ + endpoint: "candidateRetrieve", + query: { candidate_we_vote_id: we_vote_id }, + success: success + }); + }, + + 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", + success: success || defaultSuccess }); + }, + + positionListForBallotItem: function (id, kind_of_ballot_item, success) { + return get({ + endpoint: "positionListForBallotItem", + query: { + ballot_item_id: id, + kind_of_ballot_item: kind_of_ballot_item + }, + success: success || defaultSuccess + }); + }, + + 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 - }); - }, - - 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: { + }, success: success || defaultSuccess + }); + }, + + // 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 - }); - }, + }, success: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + }, + + voterStarOnSave: function (we_vote_id, success ) { + console.log("voterStarOnSave, we_vote_id:, ", we_vote_id); + 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: success || defaultSuccess + }); + }, + + voterStarOffSave: function (we_vote_id, success ) { + console.log("voterStarOffSave, we_vote_id:, ", we_vote_id); + 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: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + }, + + 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: success || defaultSuccess + }); + } - 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({ @@ -160,12 +181,13 @@ const BallotStore = createStore({ * and callback with the ordered items * @return {Boolean} */ + initialize: function (callback) { 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) @@ -174,45 +196,42 @@ const BallotStore = createStore({ else { BallotAPIWorker - .voterBallotItemsRetrieve () + .voterBallotItemsRetrieve() .then( (res) => { - addItemsToBallotStore ( + _google_civic_election_id = res.google_civic_election_id; + + 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 ); - _ballot_order_ids.forEach( 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 ) ); @@ -223,8 +242,9 @@ const BallotStore = createStore({ .push( BallotAPIWorker - .candidatesRetrieve ( we_vote_id ) + .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 = []; @@ -233,13 +253,14 @@ 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 ); + _ballot_store [ candidate_we_vote_id ].office_display_name = office_display_name; promiseQueue - .push ( + .push( BallotAPIWorker - .positionOpposeCountForBallotItem (candidate_we_vote_id) + .positionOpposeCountForBallotItem(candidate_we_vote_id) .then( (res) => _ballot_store [ candidate_we_vote_id @@ -248,9 +269,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 @@ -262,7 +283,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 @@ -273,7 +294,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,12 +312,18 @@ 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) => { + 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; @@ -327,6 +354,64 @@ const BallotStore = createStore({ } }, + getOrFetchCandidateByWeVoteId: function (candidate_we_vote_id) { + var candidate = this.getCandidateByWeVoteId(candidate_we_vote_id); + if (candidate && candidate.is_oppose && candidate.position_list) { //candidate already retrieved + return candidate; + } + _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 (we_vote_id){ + this.fetchCandidateStarStatus(we_vote_id); + this.fetchCandidatePositions(we_vote_id); + this.fetchCandidateOffice(we_vote_id); + this.fetchCandidateOpposeCount(we_vote_id); + this.fetchCandidateSupportCount(we_vote_id); + this.fetchCandidateOpposeCount(we_vote_id); + }, + + 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); + }); + }, + + fetchCandidateStarStatus: function ( we_vote_id){ + BallotAPIWorker.voterStarStatusRetrieve(we_vote_id).then( (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); + }); + }, + + fetchCandidateOpposeCount: function (we_vote_id){ + 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); + }); + }, + /** * get ballot ordered key array and ballots * @return {Object} ordered keys and store data @@ -390,13 +475,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; }, @@ -409,9 +494,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) { @@ -429,122 +513,161 @@ 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; +} + +function addCandidateToStore (res) { + _ballot_store[res.we_vote_id] = res; + _ballot_store[res.we_vote_id].kind_of_ballot_item = "CANDIDATE"; + 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 * @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]); + 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) { + //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; } -AppDispatcher.register( action => { +// 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) { + + case BallotConstants.CANDIDATE_DETAIL_RETRIEVED: + setCandidateDetail(action.we_vote_id, action.parameter, action.parameter_alias, action.payload); + BallotStore.emitChange(); + break; + + case BallotConstants.CANDIDATE_RETRIEVED: + addCandidateToStore(action.payload); + BallotStore.emitChange(); + break; + 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; + default: + break; } }); diff --git a/src/js/stores/FacebookStore.js b/src/js/stores/FacebookStore.js index ad7feba0b..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,133 +135,119 @@ 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++) { + if (new Date().getTime() - start > milliseconds) break; } -}; +} + +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(); - - 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(); - 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(); - - function sleep(milliseconds) { - var start = new Date().getTime(); - for (var i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds){ - break; - } - } - } + //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/GuideStore.js b/src/js/stores/GuideStore.js new file mode 100644 index 000000000..203a45660 --- /dev/null +++ b/src/js/stores/GuideStore.js @@ -0,0 +1,77 @@ +import AppDispatcher from "../dispatcher/AppDispatcher"; +import GuideConstants from "../constants/GuideConstants"; + +import { $ajax } from "../utils/service"; +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; + } +}); + +const we_vote_id = "organization_we_vote_id"; + +GuideStore.dispatchToken = + AppDispatcher.register( (action) => { + switch (action.actionType) { + case GuideConstants.ORG_IGNORE: + + $ajax({ + endpoint: "organizationFollowIgnore", + data: { [we_vote_id]: action.id }, + + success: (res) => { + const { organization_we_vote_id: id, success } = res; + + if (success) { + delete _guide_store[id]; + GuideStore.emitChange(); + } + }, + + error: (err) => console.error(err) + + }); + + break; + + case GuideConstants.ORG_FOLLOW: + + $ajax({ + endpoint: "organizationFollow", + data: { [we_vote_id]: action.id }, + + success: (res) => { + const { organization_we_vote_id: id, success } = res; + + if (success) { + delete _guide_store[id]; + GuideStore.emitChange(); + } + }, + + error: (err) => console.error(err) + + }); + break; + + default: + break; + } + }); + +export default GuideStore; diff --git a/src/js/stores/PositionStore.js b/src/js/stores/PositionStore.js new file mode 100644 index 000000000..cbff5ce15 --- /dev/null +++ b/src/js/stores/PositionStore.js @@ -0,0 +1,57 @@ +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"); + +var _position_store = {}; // All positions that have been fetched (by we_vote_ids) + +const PositionAPIWorker = { + + positionRetrieve: function (we_vote_id, success) { + return get({ + endpoint: "positionRetrieve", + query: { + position_we_vote_id: we_vote_id, + }, + success: success + }); + } + +}; + +const PositionStore = createStore({ + +retrievePositionByWeVoteId: function (we_vote_id){ + PositionAPIWorker.positionRetrieve(we_vote_id, + function (res){ + PositionActions.positionRetrieved(res); + }.bind(this)); +}, + +getLocalPositionByWeVoteId: function (we_vote_id){ + return shallowClone(_position_store[we_vote_id]); +} + +}); + +function setLocalPosition (we_vote_id, position) { + _position_store[we_vote_id] = position; + return true; +} + +AppDispatcher.register(action => { + switch (action.actionType) { + + case PositionConstants.POSITION_RETRIEVED: + setLocalPosition(action.we_vote_id, action.payload ); + PositionStore.emitChange(); + break; + + } + +}); + +export default PositionStore; diff --git a/src/js/stores/VoterGuideStore.js b/src/js/stores/VoterGuideStore.js index 897eaf206..98f510ff1 100644 --- a/src/js/stores/VoterGuideStore.js +++ b/src/js/stores/VoterGuideStore.js @@ -1,56 +1,54 @@ -import BallotStore from '../stores/BallotStore'; -import { createStore } from '../utils/createStore'; -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 request = require('superagent'); -const web_app_config = require('../config'); +const cookies = require("../utils/cookies"); +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'; -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 () { + console.log(BallotStore.getGoogleCivicElectionId()); return new Promise( (resolve, reject) => request .get(`${web_app_config.WE_VOTE_SERVER_API_ROOT_URL}voterGuidesToFollowRetrieve/`) .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); - 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; } @@ -59,13 +57,17 @@ 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); + 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); + } }) - ) + ); } function addVoterGuidesFollowedToVoterGuideStore (data) { @@ -83,11 +85,12 @@ function addVoterGuidesFollowedToVoterGuideStore (data) { function retrieveOrganizations (data) { var organizations_count = 0; - return new Promise ( (resolve, reject) => _organization_list - .forEach( we_vote_id => request + return new Promise( (resolve, reject) => _organization_list + .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 }) + .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); @@ -97,7 +100,7 @@ function retrieveOrganizations (data) { organizations_count ++; if (organizations_count === _organization_list.length) { - console.log('retrieveOrganizations FOUND ALL'); + console.log("retrieveOrganizations FOUND ALL"); resolve(data); } }) @@ -109,13 +112,14 @@ 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); resolve(res.body); }) - ) + ); } function addOrganizationsFollowedToStore (data) { @@ -128,10 +132,11 @@ 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() + .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) { @@ -147,10 +152,11 @@ 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() + .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) { @@ -166,10 +172,11 @@ 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() + .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) { @@ -192,8 +199,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) @@ -206,7 +213,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)); }, /** @@ -218,8 +225,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) { @@ -234,7 +241,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)); }, /** @@ -246,7 +253,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; }, @@ -259,7 +266,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; }, @@ -309,6 +316,6 @@ AppDispatcher.register( action => { VoterGuideStore.emitChange(); break; } -}) +}); export default VoterGuideStore; diff --git a/src/js/stores/VoterStore.js b/src/js/stores/VoterStore.js index cd2ed0e6a..c72a2a4e2 100644 --- a/src/js/stores/VoterStore.js +++ b/src/js/stores/VoterStore.js @@ -1,169 +1,190 @@ -import { createStore } from '../utils/createStore'; -import { shallowClone } from '../utils/object-utils'; -import service from '../utils/service'; - -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'); - -let _location = cookies.getItem('location'); -let _position = {}; -let _voter_device_id = cookies.getItem('voter_device_id'); -let _voter_photo_url = ''; -let _voter_ids = []; +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"; + +const cookies = require("../utils/cookies"); + +let _voter_device_id = cookies.getItem("voter_device_id"); +let _location = cookies.getItem("location"); +let _voter_id = cookies.getItem("voter_id"); let _voter = {}; -const VoterAPIWorker = { - generateVoterDeviceId: function ( results ) { - console.log('generating device id...'); +const VoterStore = createStore({ - return service.get({ - endpoint: 'deviceIdGenerate', - results - }); + deviceId: function () { + return _voter_device_id; }, - createVoter: function ( results ) { - console.log('creating voter id'); + hasDeviceId: function () { + return _voter_device_id ? true : false; + }, - return service.get({ - endpoint: 'voterCreate', - results - }); + hasVoterId: function () { + return _voter_id ? true : false; + }, + + hasLocation: function () { + return _location ? true : false; }, - voterLocationRetrieveFromIP: function ( results ) { - console.log('retrieve location from IP'); + /** + * 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 + }); - return service.get({ - endpoint: 'voterLocationRetrieveFromIP', - results + 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); + } }); }, - voterRetrieve: function ( results ) { - return service.get({ - endpoint: 'voterRetrieve', - results + /** + * 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); + } }); - } -}; + }, -const VoterStore = createStore({ - get position() { return _position; }, - get voter_device_id() { return _voter_device_id; }, - get voter_photo_url() { return _voter_photo_url; }, + /** + * 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 - * @return {Boolean} + * @param {Function} callback (err, device_id) */ - initialize: function (callback) { - console.log("VoterStore.initialize"); - var voterPromiseQueue = []; - var getVoterObject = this.getVoterObject.bind(this); - - 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 ) { - 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); + getDeviceId: function (callback) { + if (callback instanceof Function === false) + throw new Error("VoterStore: getDeviceId must be called with callback"); + + if (_voter_device_id) return callback(null, _voter_device_id); + + return $ajax({ + endpoint: "deviceIdGenerate", + success: (res) => { + var { voter_device_id: id } = res; + + _setDeviceId(id); + callback(null, true, id); + }, + error: (err) => { + callback(err, true, null); + } + }); - //_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; + /** + * 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) + }); - var interval = setInterval(() => { + }, - var { we_vote_id } = response; + /** + * get the Voters location + * @param {Function} callback function (error, location) + */ + getLocation: function (callback) { + if (callback instanceof Function === false) + throw new Error("VoterStore: getDeviceId must be called with callback"); - //_voter = _voter_store [we_vote_id]; - // TODO: Deprecate this? - if ( _voter ) { - _voter_photo_url = _voter.voter_photo_url; - } + if (_location) return callback(null, _location); - if (counted.indexOf(we_vote_id) < 0) { - count += 1; // TODO Why was this 4? - counted.push(we_vote_id); - } + return $ajax({ + endpoint: "voterAddressRetrieve", + success: (res) => { + var { text_for_map_search: location } = res; - if (count === voterPromiseQueue.length && voterPromiseQueue.length !== 0) { - clearInterval(interval); - Promise.all(voterPromiseQueue).then(resolve); - } + _setLocation(location); + callback(null, location); + }, + error: (err) => callback(err) + }); - }, 1000); + }, - }).then(() => callback(getVoterObject())); - }) - ); + /** + * 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"); + var that = this; + + $ajax({ + type: "GET", + data: { text_for_map_search: location }, + endpoint: "voterAddressSave", + success: (res) => { + var { text_for_map_search: savedLocation } = res; + 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) + }); }, - getVoterObject: function () { - //console.log("VoterStore getVoterObject"); - //for (var key in _voter_store) { - // _voter = _voter_store[key]; - //} - //return _voter; - return assign({}, _voter); + signInStatus: function (callback) { + callback(assign({}, _voter)); }, /** @@ -175,15 +196,18 @@ const VoterStore = createStore({ return _voter.signed_in_personal; }, - getVoterPhotoURL: function () { - console.log("VoterStore getVoterPhotoURL"); - return _voter_photo_url; + getVoterPhotoURL: function (callback) { + 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; @@ -191,106 +215,55 @@ 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; - }, - - /** - * get the Voters location - * @return {String} location - */ - 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); +// These methods are used by the AppDispatcher code to change variables in the store +function _setLocalVoterInStore (voter) { + _voter = voter; +} - }).then(() => callback(getVoterObject())); - }) - ); - }, +function _setVoterId (id) { + _voter_id = id; + cookies.setItem("voter_id", id, Infinity); +} - /** - */ - emitChange: function () { - this.emit(CHANGE_EVENT); - }, +function _setDeviceId (id) { + _voter_device_id = id; + cookies.setItem("voter_device_id", id, Infinity); +} - /** - * @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 => { +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_LOCATION_RETRIEVE: // ChangeLocation - VoterAPIWorker - .voterLocationRetrieveFromIP( - () => VoterStore.emitChange() - ); + case VoterConstants.VOTER_CHANGE_LOCATION: // _setLocation + // Update the variable value in the store + _setLocation(action.location); + VoterStore.emitChange(); break; - case VoterConstants.VOTER_RETRIEVE: // voterRetrieve - VoterAPIWorker - .voterRetrieve( - () => VoterStore.emitChange() - ); + + case VoterConstants.VOTER_RETRIEVE: // _setLocation + // Update the variable value in the store + _setLocalVoterInStore(action.voter); + VoterStore.emitChange(); break; + default: break; } 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) { 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/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/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)); }); diff --git a/src/js/utils/service.js b/src/js/utils/service.js index c98420c36..51d62237b 100644 --- a/src/js/utils/service.js +++ b/src/js/utils/service.js @@ -3,42 +3,63 @@ * of many repetitive service calls that we will be using. * @author Nick Fiorini */ +"use strict"; -'use strict'; const DEBUG = false; +const url = require("url"); -import assign from 'object-assign'; -import * as request from 'superagent'; +const assign = require("object-assign"); +const ajax = require("../vendor/jquery.js").ajax; +const webAppConfig = require("../config"); +const cookies = require("./cookies"); -const url = require('url'); -const web_app_config = require('../config'); +import * as request from "superagent"; const defaults = { - dataType: 'json', - WE_VOTE_SERVER_API_ROOT_URL: web_app_config.WE_VOTE_SERVER_API_ROOT_URL, + dataType: "json", + baseUrl: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, + url: webAppConfig.WE_VOTE_SERVER_API_ROOT_URL, + query: {}, + type: "GET", + data: function () { + 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) }; -const service = {}; +export function $ajax (options) { + if (!options.endpoint) throw new Error("$ajax missing endpoint option"); -service.get = function (options) { + 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); + // 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) .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); - } - else { + reject(err); + } else { if (opts.success instanceof Function === true) opts.success(res.body); else if (DEBUG) @@ -48,6 +69,4 @@ service.get = function (options) { } }) ); -}; - -export default service; +} 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 diff --git a/src/sass/base/_base.scss b/src/sass/base/_base.scss index 19f6e3f70..7c1adc5d1 100644 --- a/src/sass/base/_base.scss +++ b/src/sass/base/_base.scss @@ -1,7 +1,16 @@ +body, html { + height: 100%; + min-height: 100%; +} + html { box-sizing: border-box; } +#app { + height: 100%; +} + *, *::before, *::after { box-sizing: inherit; } 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 46f0a2b66..a61559f0c 100644 --- a/src/sass/components/_candidate.scss +++ b/src/sass/components/_candidate.scss @@ -1,37 +1,40 @@ // scoped variables -$smallFont: 1.3em; +$smallFont: 1.0em; $mediumFont: 1.6em; $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; + } + } + .support-emphasis-large { + color: rgba(63,191,63, 1); } .oppose-emphasis-medium { - color: red; - font-size: $mediumFont; + color: rgba(215, 73, 55, 1); + } + .oppose-emphasis-medium, .support-emphasis-medium { + font-size: $smallFont; font-weight: 500; } - .oppose-emphasis-large { - color: red; - font-size: $largeFont; + .oppose-emphasis-large, .support-emphasis-large { + font-size: $smallFont; font-weight: 700; } .support { padding-right: 1em; } - .support-emphasis-small { - font-size: $smallFont; - font-weight: 300; - } - .support-emphasis-medium { - font-size: $mediumFont; + .running-for-office-emphasis { + font-size: 1.3em; font-weight: 500; } - .support-emphasis-large { - font-size: $largeFont; - font-weight: 700; + .link-text-to-more-info { + font-size: 0.8em; + font-weight: 100; } } 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/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/_itemActionbar.scss b/src/sass/components/_itemActionbar.scss index a9c4a939e..d765b5d4d 100644 --- a/src/sass/components/_itemActionbar.scss +++ b/src/sass/components/_itemActionbar.scss @@ -1,9 +1,24 @@ -.item-actionbar { +.item-actionbar .inline-phone:hover { + cursor: pointer; +} +.item-actionbar, .item-actionbar2 { border-top: 1px solid #DDD; padding:10px 5px; .action { width: 25%; } + .support-emphasis { + color: rgba(63,191,63, 1); + } + .oppose-emphasis { + color: rgba(215, 73, 55, 1); + } + + @media all and (max-width: 480px) { + .inline-phone { + font-size: .9em; + } + } } 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/components/_organization.scss b/src/sass/components/_organization.scss new file mode 100644 index 000000000..60bc58ad1 --- /dev/null +++ b/src/sass/components/_organization.scss @@ -0,0 +1,20 @@ +.organization img { + margin-top:19px; +} +.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/components/_position.scss b/src/sass/components/_position.scss new file mode 100644 index 000000000..0faddba2a --- /dev/null +++ b/src/sass/components/_position.scss @@ -0,0 +1,7 @@ +.position-item { + .position-source { + color: rgba(171,171,171, 1); + font-size: 0.9em; + font-weight: 200; + } +} 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/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/_header.scss b/src/sass/layout/_header.scss index 6be9dfbb7..890326b56 100644 --- a/src/sass/layout/_header.scss +++ b/src/sass/layout/_header.scss @@ -1,3 +1,34 @@ // ---------------------------------------------------------------------------- // This file contains all styles related to the header of the app // ---------------------------------------------------------------------------- + +.header { + &-address { + color: #D8D8D8; + margin-right: 200px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + width: 30%; + } +} + +.header { + &-version { + color: rgba(171,171,171, 1); + font-size: 0.7em; + font-weight: 200; + overflow: hidden; + white-space: nowrap; + } +} + +@media all and (max-width : 659px) { + .header { + &-address { + margin-right: 10px; + text-align: right; + } + } +} diff --git a/src/sass/layout/_mediaquery.scss b/src/sass/layout/_mediaquery.scss index ef361c758..c606b01b4 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: 220px; + z-index: 2; + } + } + } + .icon { + &-hamburger { + &--mobile { + display: inline-block; + } + } + } +} @media all and (min-width : 660px) { @@ -29,7 +65,7 @@ display: inline-block; height: 45px; left: auto; - right: 100px; + right: 0; top: 0; .separate-top { @@ -62,6 +98,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; @@ -69,4 +114,10 @@ margin: 0 auto; width: 960px; } + + .headroom--pinned { + margin: 0 auto; + max-width: 958px; + width: 958px; + } } diff --git a/src/sass/main.scss b/src/sass/main.scss index fe6471cd3..4357bdde9 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -31,7 +31,13 @@ './components/_badges', './components/_ballotList', './components/_itemActionbar', - './components/_candidate.scss'; + './components/_candidate', + './components/_starAction', + './components/_position', + './components/_navigator', + './components/_ignore', + './components/_organization', + './components/_header'; // 6. Page-specific styles @import 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; +} 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; 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(); +}); diff --git a/unclesamewevote.jpg b/unclesamewevote.jpg new file mode 100644 index 000000000..85e69e6e9 Binary files /dev/null and b/unclesamewevote.jpg differ diff --git a/wevotelogo.png b/wevotelogo.png new file mode 100644 index 000000000..2971796a6 Binary files /dev/null and b/wevotelogo.png differ