├── .gitignore ├── .sonarcloud.properties ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── develop.md ├── dist └── upvotejs │ ├── upvotejs.css │ ├── upvotejs.jquery.js │ ├── upvotejs.svg │ └── upvotejs.vanilla.js ├── images ├── feature.png └── forkme_right_red_aa0000.png ├── index.html ├── package-lock.json ├── package.json ├── tests ├── jquery.html ├── lib │ ├── common-tests.js │ ├── setup.js │ └── utils.js ├── test.bootstrap.js ├── test.jquery.js ├── test.vanilla.js └── vanilla.html └── upvote.jquery.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .nyc_output 4 | .coverage 5 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | sonar.sources=dist 3 | #sonar.exclusions= 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | sonar.tests=tests 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | 11 | # Source encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | # Exclusions for copy-paste detection 15 | #sonar.cpd.exclusions= 16 | 17 | sonar.projectVersion=2.1.0 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.2.0] - 2019-02-26 11 | 12 | ### Changed 13 | 14 | - Replace qunit tests with mocha 15 | 16 | ### Added 17 | 18 | - Add test coverage reporting 19 | - Integrate with Travis CI 20 | - Integrate with sonarcloud.io (autoscan mode) 21 | 22 | ## [2.1.0] - 2019-02-09 23 | 24 | ### Added 25 | 26 | - This changelog (recent history written in retrospect) 27 | - Add user action to JSON payload in callbacks 28 | 29 | ### Changed 30 | 31 | - Move data to newState field in JSON payload in callbacks (API breaking) 32 | 33 | ## [2.0.0] - 2019-02-05 34 | 35 | ### Changed 36 | 37 | - Rewrite in vanilla JavaScript 38 | - Update widget style to match current design on Stack Exchange 39 | - Rewrite entire codebase 40 | 41 | ## [1.0.0] - 2013-07-04 42 | 43 | ### Added 44 | 45 | - Voting widget like the one used on Stack Exchange sites 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UpvoteJS 2 | ======== 3 | 4 | [](https://travis-ci.org/janosgyerik/upvotejs) 5 | 6 | `UpvoteJS` is a JavaScript package to create a voting widget that looks like 7 | the one used on [Stack][stackoverflow] [Exchange][superuser] [sites][serverfault]. 8 | For example, like this: 9 | 10 | ![Screenshot featuring stackoverflow.com, superuser.com, serverfault.com][feature] 11 | 12 | Using the package 13 | ----------------- 14 | 15 | There are different ways to use the package, depending on your use case. 16 | 17 | ### Create a "read-only" widget in HTML 18 | 19 | That is, have a nicely rendered widget based on HTML markup alone, 20 | to simply represent some state, that cannot be modified by user action. 21 | In this mode, no JavaScript is needed, and the counter, the upvote/downvote arrows 22 | and the star are rendered based on the HTML markup alone. 23 | 24 | Include the stylesheet in your page's `
` element, for example: 25 | 26 | 27 | 28 | Make sure the image `upvotejs.svg` is present in the same directory as the `upvotejs.css` file. 29 | 30 | Include this basic HTML boilerplate for each vote: 31 | 32 | 38 | 39 | Customize the boilerplate with values appropriate for the vote you want to display: 40 | 41 | - Set the correct value for `.count` 42 | - For upvoted state, add class `upvote-on` for `.upvote` 43 | - For downvoted state, add class `downvote-on` for `.downvote` 44 | - For starred state, add class `star-on` for `.star` 45 | 46 | With only HTML code, the widget is read-only, the voting and star buttons are not clickable. 47 | 48 | ### Create an interactive widget 49 | 50 | That is, enable user interactions to upvote, downvote, or star, 51 | modifying the state of the counter. 52 | However, storing the state on some backend is still out of scope in this mode. 53 | 54 | Include the JavaScript sources in your page's `` element, for example: 55 | 56 | 57 | 58 | Create the Upvote widget controller: 59 | 60 | Upvote.create('id'); 61 | 62 | Where `id` is the ID of the widget in the DOM. 63 | 64 | With this step, the controls of the widget will become clickable, upvoting and downvoting will update the count and indicate toggled state, so will the star, and consistent state will be enforced. 65 | 66 | With HTML and JavaScript code alone, the state of the widget will not be persisted in any storage. 67 | 68 | ### Save the state of the widget to some backend 69 | 70 | In order to save the state of the widget in response to user action, 71 | pass a callback handler when creating the widget, for example: 72 | 73 | Upvote.create('id', {callback: your_callback_handler}); 74 | 75 | On any change to the state of the widget (vote up, vote down, or star), 76 | the callback handler will be called, with a JSON object as parameter, 77 | with fields: 78 | 79 | - `id`: the id of the DOM object, the same value that was used when creating with `Upvote.create` 80 | 81 | - `action`: the user action that triggered the state change. 82 | Possible values: `upvote`, `unupvote`, `downvote`, `undownvote`, `star`, `unstar` 83 | 84 | - `newState`: a JSON object with fields: 85 | - `count`: the current vote count 86 | - `upvoted`: `true` if the widget is in upvoted state 87 | - `downvoted`: `true` if the widget is in downvoted state 88 | - `starred`: `true` if the widget is in starred state 89 | 90 | Note that `upvoted` and `downvoted` will never be `true` at the same time. 91 | 92 | An example payload: 93 | 94 | { 95 | id: 'my-vote', 96 | action: 'upvote', 97 | newState: { 98 | count: 123, 99 | upvoted: true, 100 | downvoted: false, 101 | starred: true 102 | } 103 | } 104 | 105 | Using this data object, it is up to your implementation of `your_callback_handler` to actually implement writing the new state on some backend, for example with a `POST` or `PATCH` call to a storage service to update the user's vote. 106 | 107 | ### Using with jQuery 108 | 109 | It's possible to use the package through jQuery, if you prefer (though not clear why). 110 | 111 | Include the following in ``: 112 | 113 | 114 | 115 | 116 | 117 | Make sure the image `upvotejs.svg` is present in the same directory as the `upvotejs.css` file. 118 | 119 | Initialization examples: 120 | 121 | $('#topic').upvote(); 122 | $('#topic').upvote({count: 5, upvoted: true}); 123 | $('#topic').upvote({count: 5, downvoted: true}); 124 | $('#topic').upvote({count: 5, upvoted: true, starred: true}); 125 | 126 | var callback = function(data) { 127 | $.ajax({ 128 | url: '/vote', 129 | type: 'post', 130 | data: data 131 | }); 132 | }; 133 | $('#topic-123').upvote({id: 123, callback: callback}); 134 | 135 | Methods: 136 | 137 | // Create, pick up initial values from HTML markup 138 | $('#topic').upvote(); 139 | 140 | // Mutators 141 | $('#topic').upvote('upvote'); // Upvote! 142 | $('#topic').upvote('downvote'); // Downvote! 143 | $('#topic').upvote('star'); // Star! 144 | 145 | // Getters 146 | $('#topic').upvote('count'); // Get the current vote count 147 | $('#topic').upvote('upvoted'); // Get the upvoted state -> boolean 148 | $('#topic').upvote('downvoted'); // Get the downvoted state -> boolean 149 | $('#topic').upvote('starred'); // Get the starred state -> boolean 150 | 151 | API reference 152 | ------------- 153 | 154 | Files: 155 | 156 | - `upvotejs.css` and `upvotejs.svg` are required for styling, and both must be in the same directory 157 | - `upvotejs.vanilla.js` is required for interactive use 158 | 159 | Create an Upvote widget controller using `Upvote.create`: 160 | 161 | const widget = Upvote.create(id, params = {}); 162 | 163 | An element in the DOM must exist with the specified `id`, and have a basic markup like this: 164 | 165 | 171 | 172 | The widget will be parameterized based on the markup of the DOM element: 173 | 174 | - The count is read from the value of `.count` 175 | - The state is considered upvoted if `.upvote` has `upvote-on` class. 176 | - The state is considered downvoted if `.downvote` has `downvote-on` class. 177 | - The state is considered starred if `.star` has `star-on` class. 178 | 179 | An exception will be thrown in case of invalid DOM markup: 180 | 181 | - If a DOM element with the specified `id` doesn't exist 182 | - If the DOM element with `id` is already used by another Upvote widget controller 183 | - If the DOM element doesn't have all required components 184 | - If both upvoted and downvoted state are detected at the same time 185 | 186 | The state values can be overridden explicitly using the `param` object, with fields: 187 | 188 | - `count` - the vote count to set, must be an integer 189 | - `upvoted` - a boolean to set upvoted state 190 | - `downvoted` - a boolean to set downvoted state 191 | - `starred` - a boolean to set starred state 192 | - `callback` - a function that will be called on any state change 193 | 194 | An exception will be thrown in case of invalid parameters: 195 | 196 | - If the parameter types don't match expected values 197 | - If `upvoted` and `downvoted` are both `true` 198 | 199 | ### Properties 200 | 201 | An instance of an Upvote widget controller has the following properties: 202 | 203 | - `id`: the `id` of the controller 204 | - `count()`: get the current count 205 | - `upvote()`: toggle upvoted state 206 | - `upvoted()`: get the upvoted state 207 | - `downvote()`: toggle downvoted state 208 | - `downvoted()`: get the downvoted state 209 | - `star()`: toggle starred state 210 | - `starred()`: get the starred state 211 | 212 | ### Event handling 213 | 214 | The widget controller, on its creation, overwrites the `onclick` property of the selected DOM. 215 | 216 | On `destroy`, the widget controller resets `onclick` to `null`. 217 | 218 | Sponsors 219 | -------- 220 | 221 | Contributions from users and sponsors help make UpvoteJS possible. 222 | We accept donations via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SQTLZB5QCLR82). Thanks! :) 223 | 224 | License 225 | ------- 226 | 227 |