Skip to content

CodeForPhilly/philly-ward-leaders

Repository files navigation

philly-ward-leaders

Shining a spotlight on the powerful players behind Philadelphia elections. Demo of v2.

Had you heard of all the candidates for judge last time you voted? How about City Council at-Large? When you voted, a neighbor of yours likely handed you a ballot of candidates endorsed by your ward. If you haven’t heard of the candidates, why not take your neighbor’s advice?

These endorsements are determined by Ward Leaders, and have a huge influence over who gets elected in Philadelphia. As powerful and few as they are (69), ward leaders tend to remain behind the scenes and most voters don’t even know who theirs is. This transparency web site aims to bring a level of spotlight to ward leaders that is more proportional to the power they wield.

Local development

This project currently uses node version 20 to build.

You can use Docker to start up a container that has node 20 if you don't have it for local development. The lines with ">" are in the native shell, the lines with ~ are in Docker.

# Build and start the Docker container.
>  docker compose build
>  docker compose up -d
>  docker compose exec ward-leaders bash

# Install dependencies in the Docker container
~ npm ci

# In Docker, serve at localhost:8080
~ npm run dev

# Inital load of the app in your browser is slow, fyi.
# The dev server has hot reload - you should see any
# changes reflected in the browser in about a second.
# Use ctrl-C to exit the dev server.

# In Docker, build for production with minification
~ npm run build

# Exit docker and stop the container
~ exit

>  docker compose down

Technical overview

Philly Ward Leaders is a JavaScript single page application built on the Vue.js framework. In addition to its source code, this application is powered by data (about each ward leader and committee person) and some narrative content. In order for this data and content to be easily updated by staff at the Committee of Seventy, it’s housed in Contentful, a content management SaaS product with a user-friendly editing interface (similar to WordPress, but just the admin half). At runtime, the JavaScript application fetches the content and data necessary to render each page via Contentful’s REST API.

Concepts

This codebase assumes you’re familiar with the following concepts.

Ajax and REST APIs This application uses a JavaScript concept called Ajax (albeit heavily abstracted by libraries) to fetch data from Contentful using their REST API.

Node.js and npm While this application runs in the browser, completely client-side, Node.js is used to compile the application before-hand, and npm is used to manage its dependencies.

Contentful Poking around their website may be enough, but it would be helpful to create a free account and poke around to make sure you understand the concept of a CMS-as-a-Service.

Vite Vite Is used to build and combine JavaScript modules into a bundled file that can be run in the browser. In this project it’s also used to run a local development server and the server for e2e tests.

Modern JavaScript features This application is written in modern JavaScript (ES2015-ES2017) with language features such as arrow functions, destructuring, object spread operator, and async/await.

Vue.js We chose this framework because it’s relatively easy to get up-to-speed in, even if you’ve never used a JS framework before. But there are probably a few Vue.js-only things you’ll find yourself scratching your head about if it’s your first Vue.js app. It would be worth reading through their really great guide. This project was initially developed with Vue 2 and later upgraded to Vue 3. It uses the Options API.

Composing components This is a concept that you’ll already know if you’ve used React, Angular, choo, or other modern JS frameworks. If not, you’ll come across it in the Vue.js guide. React’s docs are also helpful for the concept.

Vuex We use Vue.js’ centralized state management library, vuex. If you’re familiar with flux, redux, or elm, this will be pretty recognizable. If it’s your first time with centralized state management, this may be the most complex concept. Read over the vuex docs — specifically “What is vuex?”

Vue-router If you’ve used a router before, whether in JS or a server-side environment, this should seem pretty familiar. But it will be helpful to have the vue-router docs handy for anything that’s not obvious.

Testing Cypress Only the Cypress component and e2e tests are currently being utilized. The tests are run automatically using github actions, but also can be run locally with npm.

Directory structure

.
├── 200.html -> index.html
├── LICENSE
├── README.md
├── data-scripts
├── package.json
├── index.html
├── public
│   ├── data
│   ├── CNAME
│   ├── 404.html
├── src
│   ├── App.vue
│   ├── api
│   ├── assets
│   ├── components
│   ├── config.js
│   ├── main.js
│   ├── router
│   ├── store
│   │   ├── actions.js
│   │   ├── getters.js
│   │   ├── index.js
│   │   └── mutations.js
│   ├── util.js
│   └── views
├── cypress
└── vite.config.js
└── cypress.config.js

src directory

File / directory Description
index.html Vite uses this as the basis for serving the site. Inline JS code works together with code in 404.html to made this work as a single page app on github pages.
public Static assests that are copied to the build to be deployed. The build script will put additional (compiled) files together with these files and index.html in a build/ directory.
public/data Static data files that are requested dynamically by the application (at runtime).
main.js Primary entry point for the app. Initializes router, store, and the top-level Vue component, mounting it to the #app element in index.html.
App.vue Top-level Vue component. Provides the layout for the site, including the nav bar, loading indicator, and space for the current route’s view. Also sets up the site’s core styles.
config.js Configuration values which are not sensitive. Any values needed by the app at runtime should be considered “public” in a JavaScript single page app like this.
util.js Utility functions used in multiple files.
assets Static assets
router Maps URL paths to corresponding views, which are surfaced in the <router-view> component of App.vue.
views High-level components representing the “pages” of the app. Views render content, including other components. Views are also components, but what makes them “higher level” is that they represent a larger surface area, are very unlikely to be reused, and typically interact with the app’s store by triggering data fetches and using the store’s state.
components Low-level components that represent a distinct section, item, or functionality and may be reusable. By convention, components don’t interact with the store. If they need to render a piece of state from the store, the view that composes the component will pass that piece of state to it as a prop. If they need to trigger an action in the store (ie. in response to clicking on the component), the component should emit an event that the view listens to.
store/index.js Initializes the application’s store and defines the pieces of state as well as their initial values.
store/actions.js Functions that have side effects (ie. fetching data from a server), and typically save their results to the state by passing it to a mutation.
store/mutations.js Functions that change (mutate) the state. They’re typically called by actions, but could also be called by views (if no side effect is necessary). Note that Vue updates state mutably because YOLO, but has a couple minor caveats when adding or removing object properties.
store/getters.js Reusable convenience functions for deriving/computing values from the state.
api Abstraction of the actual interactions with servers. These functions are called by actions and could live inside them but are separated for readability.
data-scripts Handy little python scripts to clean and merge voter turnout and registration data. Also a script to migrate content to contentful. See data-scripts/README.md for details.

Browser testing

BrowserStack Logo BrowserStack kindly provides free access to their cross-browser testing platform since this is an open source project.