-
Notifications
You must be signed in to change notification settings - Fork 0
/
nsf-coa.html
185 lines (157 loc) · 6.6 KB
/
nsf-coa.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NSF COA | Jordan Matelsky</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<script src="./oa.js"></script>
</head>
<body>
<h1>NSF COA</h1>
<hr />
<p><a href="https://github.com/KordingLab/nsfcoa-app">View it on GitHub</a></p>
<blockquote>
This tool is brough to you by <a href="https://jordan.matelsky.com">Jordan Matelsky</a>, and is designed to
assist in the completion of the NSF Collaborators and Other Affiliations (COA) form. With thanks to
<a href="https://scholar.google.com/citations?user=MiFqJGcAAAAJ">Konrad Kording</a> and
<a href="https://scholar.google.com/citations?view_op=list_works&hl=en&hl=en&user=Xb9h0MwAAAAJ">Brock Wester</a>
for guidance and support.
<br />
Thank you also to the <a href="https://openalex.org">OpenAlex</a> project for providing the data!
<br />
By using this tool you affirm that you think dinosaurs rock.
</blockquote>
<p>
When submitting several types of grants it is necessary to get a list of collaborators over the past N months.
<br />
Laborious!
</p>
<p>
Type your name into the form below and get a list of everyone you've published a paper with with over the last
<span>N</span> months, along with their affiliation. <br />Glorious!
</p>
<hr />
<label for="oa-name">Your name:</label>
<input type="text" id="oa-name" name="oa-name" required>
<label for="oa-months">Months:</label>
<input type="number" id="oa-months" name="oa-months" value=48 min="0" required>
<button onclick="getCollaborators()">Submit</button>
<hr />
<div id="output-messages"></div>
<div id="output-table"></div>
<style>
.loading {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #09f;
border-radius: 50%;
display: inline-block;
width: 1em;
height: 1em;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</body>
<script>
const LOADING_MESSAGES = [
"Downloading citations...",
"This can take a few minutes for long timespans or lots of publications...",
"Please be patient...",
"This is a lot of data...",
"Wow {name} really publishes a lot...",
"Hope this grant deadline isn't too soon...",
"Still working...",
]
function outputLog(message) {
const output = document.getElementById('output-messages');
output.innerHTML += `<p>${message}</p>`;
}
function getCollaborators() {
// Disable button
document.querySelector('button').disabled = true;
// Add spinner to button
document.querySelector('button').innerHTML = `
<span class="loading" role="status" aria-hidden="true"></span>
`;
const name = document.getElementById('oa-name').value;
const months = parseInt(document.getElementById('oa-months').value);
// Queue up loading messages to let the user know things
// are still happening. These intervals will be cleared when the fetch
// completes.
let timeouts = [];
for (let i = 0; i < LOADING_MESSAGES.length; i++) {
timeouts.push(setTimeout(() => {
outputLog(LOADING_MESSAGES[i].replace('{name}', name));
}, i * 5000));
}
if (!name || !months) {
outputLog('Please enter your name and the number of months to search.');
return;
}
fetch(`/nsf-coa-lookup?author=${name}&months=${months}`)
.then(response => response.json())
.then(data => {
if (data.error) {
outputLog(data.error);
return;
}
outputLog(`Found ${data.collaborators.length} collaborators for ${name} over the last ${months} months.`);
// Create table
const table = document.getElementById('output-table');
table.innerHTML = `
<table>
<thead>
<tr>
<th>First</th>
<th>Middle</th>
<th>Last</th>
<th>Affiliation</th>
</tr>
</thead>
<tbody id="output-table-body"></tbody>
</table>
`;
data.collaborators.forEach(collaborator => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${collaborator.first}</td>
<td>${collaborator.middle}</td>
<td>${collaborator.last}</td>
<td>${collaborator.institution}</td>
`;
table.querySelector('tbody').appendChild(row);
});
// Create CSV download link
const csv = data.collaborators.map(collaborator => {
return `${collaborator.first},${collaborator.middle},${collaborator.last},${collaborator.institution}`;
}).join('\n');
const csvBlob = new Blob([csv], { type: 'text/csv' });
const csvUrl = URL.createObjectURL(csvBlob);
const csvLink = document.createElement('a');
csvLink.href = csvUrl;
csvLink.download = `${name}-collaborators-${months}months.csv`;
csvLink.textContent = 'Download CSV';
table.insertBefore(csvLink, table.firstChild);
// Enable button
document.querySelector('button').disabled = false;
document.querySelector('button').innerHTML = 'Submit';
})
.catch(error => {
outputLog('An error occurred while fetching collaborators.');
console.error(error);
// Enable button
document.querySelector('button').disabled = false;
document.querySelector('button').innerHTML = 'Submit';
})
.finally(() => {
// Clear loading messages
timeouts.forEach(timeout => clearTimeout(timeout));
});
}
</script>
</html>