├── .gitignore ├── README.md └── donor_reveal.js /.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore these directories 2 | 3 | #ignore these files 4 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Donor Reveal 2 | 3 | Donor-reveal is code that allows users to instantly reveal who donated to any current lawmakers mentioned in a story filed in @NCCapitol. It uses data from the National Institute on Money in State Politics. 4 | 5 | By hovering over a lawmaker's name, a pop-up will show basic information, as well as donation totals and the lawmaker's top five contributors. 6 | 7 | The WRAL version was inspired by the [Greenhouse Chrome extension](http://allaregreen.us/), created by Nicholas Rubin, to track money in Congress. 8 | 9 | See an example here: 10 | 11 | http://wral.com/14509748 12 | 13 | ## NOTE 14 | 15 | We're storing code here mostly for internal purposes. Additional code to scrape and store data from the followthemoney.org API (built into the WRAL CMS) is required to create a working version of Donor Reveal. 16 | 17 | ## How it works 18 | 19 | The most basic building block of donor-reveal data is WRAL's dataset of current state lawmakers in the House and Senate. 20 | 21 | Donor-reveal relies heavily on campaign funding data from the [National Institute on Money in State Politics](http://followthemoney.org) and its open application programming interface, or API. WRAL.com regularly runs "data ingestors" that gather the most recently updated information available about each of the state's 170 state House and Senate members based on the names and IDs listed in WRAL's lawmakers dataset. 22 | 23 | When a user loads a story filed in @NCCapitol, the donor-reveal script tries to find any full name listed in the lawmaker dataset (names are case-sensitive and must be spelled exactly the same as those in the database). If names are found, the script uses special highlighting on the text. It retrieves the bio information, contribution totals and top-five contributors from the data store and feeds them into a box that will appear when users hover over the highlighted text (or click/tap on mobile). -------------------------------------------------------------------------------- /donor_reveal.js: -------------------------------------------------------------------------------- 1 | /*donor-reveal_v0.82 2 | Allows users to instantly reveal who donated to any current lawmakers 3 | mentioned in a story filed in @NCCapitol. 4 | It uses data from the National Institute on Money in State Politics 5 | Created by Tyler Dukes and Alex Phillips 6 | Last updated 6.18.15, 10:08 a.m. 7 | */ 8 | require([ 9 | 'jquery', 10 | 'lodash', 11 | 'jquery-ui' 12 | ], function ($, _) { 13 | 14 | //Global variables 15 | var ncga_members = "not run"; 16 | var ftm_data; 17 | var ftm_total; 18 | var pol_data = {}; 19 | var opened_tooltip; 20 | 21 | //Load members of the NCGA 22 | var ncga_members = (function () { 23 | $.ajax({ 24 | 'async': true, 25 | 'global': false, 26 | 'url': '/news/state/nccapitol/data_set/14376504/?dsi_id=ncga-eid&version=jsonObj', 27 | 'dataType': "json", 28 | 'success': function (data) { 29 | ncga_members = data; 30 | highlightText(); 31 | } 32 | }); 33 | return ncga_members; 34 | })(); 35 | 36 | //Query the FollowTheMoney.com API 37 | function getDonations(eid, wral_id) { 38 | $.ajax({ 39 | 'type': 'GET', 40 | 'async': true, 41 | 'url': '/news/state/nccapitol/data_set/14563916/?dsi_id=' + eid + '&version=jsonObj', 42 | 'dataType': "json", 43 | 'success': function (data) { 44 | //clear old data 45 | ftm_data = ""; 46 | //just load the first five items for display 47 | $.each(data.slice(0, 5), function (index, value) { 48 | //If statement here checks for lawmakers with no records 49 | if (value["total"] == "No Records") { 50 | ftm_data = 'No records$0'; 51 | ftm_total = '$0'; 52 | } 53 | else { 54 | ftm_data += '' + (value["contributor"]).replace(/\w\S*/g, function (txt) { 55 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 56 | }) + 57 | '$' + (Math.round(value["total"]) + "").replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,") + ''; 58 | ftm_total = '$' + (Math.round(data[0]["total_hide"]) + "").replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,"); 59 | } 60 | }); 61 | pol_data[wral_id][0] = ftm_total; 62 | //add value to the pol_data object 63 | pol_data[wral_id][1] = ftm_data; 64 | //add value info was last updated 65 | pol_data[wral_id][2] = data[0]["updated_hide"]; 66 | //when finished, close the tool tip and reopen by replacing the spinner with real data 67 | $(".donor-reveal").tooltip('close'); 68 | $(".donor-reveal#" + opened_tooltip).tooltip('open'); 69 | } 70 | }); 71 | } 72 | 73 | //define tooltip structure and pass in variables 74 | function ncgaBox(name, party, district, img, position, wral_id) { 75 | return '
' + 76 | '' + 77 | '

' + position + ' X 
' + 78 | '' + name + '
' + 79 | '' + party + '-' + district + '' + 80 | '

2013-14 donations:
' + 81 | pol_data[wral_id][0] + '

' + 82 | '

Top donors since 2013

' + 83 | '' + 84 | '' + 85 | '' + 86 | '' + 87 | '' + 88 | pol_data[wral_id][1] + 89 | '
ContributorTotal
' + 90 | '

Source: followthemoney.org
' + 91 | 'Data as of ' + pol_data[wral_id][2] + '

' + 92 | '
'; 93 | } 94 | 95 | //For clicking/tapping to close 96 | $('.donor-box').click(function () { 97 | $(".donor-reveal").tooltip('close'); 98 | }); 99 | 100 | //Script to find lawmakers and initialize distinct tooltips 101 | function highlightText() { 102 | //Make a list of lawmakers from the loaded JSON, then search for them 103 | for (var i = 0; i < ncga_members.length; i++) { 104 | $.each($(".story-text").children(), function() { 105 | if (!$(this).is('p')) { 106 | return; 107 | } 108 | 109 | /* 110 | Regex will prevent matching variable that is between HTML tags 111 | RegExp Breakdown: 112 | 113 | ( # Open capture group 114 | variable # Match variable text 115 | ) # End capture group 116 | (?! # Negative lookahead start (will cause match to fail if contents match) 117 | [^<]* # Any number of non-'<' characters 118 | > # A > character 119 | | # Or 120 | [^<>]* # Any number of non-'<' and non-'>' characters 121 | <\/ # The characters < and /, with forward slash escaped 122 | ) # End negative lookahead. 123 | */ 124 | var regex = new RegExp("(" + ncga_members[i]["member"] + ")(?![^<]*>|[^<>]*<\/)"); 125 | replaceMarkup ( 126 | $(this), 127 | regex, 128 | '' 129 | ); 130 | 131 | //and check for alternates, but only if the alt_spelling column isn't blank 132 | if (ncga_members[i]["alt_spelling"] != "") { 133 | regex = new RegExp("(" + ncga_members[i]["alt_spelling"] + ")(?![^<]*>|[^<>]*<\/)"); 134 | replaceMarkup( 135 | $(this), 136 | regex, 137 | '' 138 | ); 139 | } 140 | }); 141 | } 142 | 143 | function replaceMarkup($element, regex, markup) { 144 | $element.html($element.html().replace(regex, markup)); 145 | } 146 | 147 | //On successful highlight, grab all IDs with class donor-reveal and get JSON data 148 | $.each($('.donor-reveal'), function () { 149 | //initialize with a loading spinner 150 | pol_data[this.id] = ["", 'Loading...', ""]; 151 | getDonations(ncga_members[this.id]["eid"], this.id) 152 | }); 153 | 154 | //add tooltip 155 | $(".donor-reveal").tooltip({ 156 | items: "button", 157 | open: function () { 158 | opened_tooltip = $(this).attr('id') 159 | }, 160 | content: function () { 161 | var id = $(this).attr('id') 162 | return ncgaBox(ncga_members[id]["member"], 163 | ncga_members[id]["party"], 164 | ncga_members[id]["county_short"], 165 | ncga_members[id]["headshot"], 166 | ncga_members[id]["title"], 167 | id 168 | ); 169 | } 170 | }); 171 | } 172 | }); --------------------------------------------------------------------------------