GRO Index Search Helper

Adds additional functionality to the UK General Register Office (GRO) BMD index search

当前为 2018-04-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GRO Index Search Helper
  3. // @description Adds additional functionality to the UK General Register Office (GRO) BMD index search
  4. // @namespace cuffie81.scripts
  5. // @include https://www.gro.gov.uk/gro/content/certificates/indexes_search.asp
  6. // @version 1.11
  7. // @grant GM_listValues
  8. // @grant GM_getValue
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.10/handlebars.min.js
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js
  12. // ==/UserScript==
  13.  
  14.  
  15. this.$ = this.jQuery = jQuery.noConflict(true);
  16.  
  17. $(function() {
  18. var resources, recordType, results;
  19. var main = function() {
  20. buildResources();
  21. recordType = getRecordType();
  22. //console.log("resources:\r\n%s", JSON.stringify(resources));
  23. // Load the general css
  24. $("body").append($(resources.baseStyle));
  25.  
  26. initialiseSearchForm();
  27. initialiseResultViews();
  28. // Scroll down to the form. Do this last as we may add/remove/chnage elements in the previous calls.
  29. $("h1:contains('Search the GRO Online Index')")[0].scrollIntoView();
  30. // Wire up accesskeys to clicks, to avoid having to use the full accesskey combo (eg ALT+SHFT+#)
  31. $(document).on("keypress", function(e) {
  32. if (!document.activeElement || document.activeElement.tagName.toLowerCase() !== "input")
  33. {
  34. var char = String.fromCharCode(e.which);
  35. //console.log("keypress: %s", char);
  36. if ($("*[id^='groish'][accesskey='" + char + "']").length)
  37. $("*[id^='groish'][accesskey='" + char + "']").click();
  38. else if (char == "{")
  39. adjustSearchYear(-10);
  40. else if (char == "}")
  41. adjustSearchYear(10);
  42. else if (char == "?")
  43. $("form[name='SearchIndexes'] input[type='submit']").click();
  44. else if (char == '@')
  45. switchRecordType();
  46. }
  47. });
  48. }
  49. var initialiseSearchForm = function() {
  50. // Hide superfluous spacing, text and buttons
  51. $("body > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2)").hide();
  52. $("h1:contains('Search the GRO Online Index')").closest("tr").next().hide();
  53. $("strong:contains('Which index would you like to search?')").closest("tr").hide();
  54. $("table[summary*='contains the search form fields'] > tbody > tr:nth-of-type(2)").hide();
  55. $("table[summary*='contains the search form fields'] > tbody > tr:nth-of-type(3) td.main_text[colspan='5']").parent().hide();
  56. $("form[name='SearchIndexes'] input[type='submit'][value='Reset']").hide();
  57. $("form[name='SearchIndexes'] a.tooltip").hide();
  58. // Change text
  59. $("form[name='SearchIndexes'] td span.main_text:contains('year(s)')").text("yrs");
  60. $("form[name='SearchIndexes'] td.main_text:contains('Surname at Death:')").html("Surname:<span class='redStar'>*</span>");
  61. $("form[name='SearchIndexes'] td.main_text:contains('First Forename at Death:')").text("Forename 1:");
  62. $("form[name='SearchIndexes'] td.main_text:contains('Second Forename at Death:')").text("Forename 2:");
  63. $("form[name='SearchIndexes'] td.main_text:contains('District of Death:')").text("District:");
  64. $("form[name='SearchIndexes'] td.main_text:contains('Age at'):contains('Death'):contains('in years')").text("Age:");
  65. $("form[name='SearchIndexes'] td.main_text:contains('Surname at Birth:')").html("Surname:<span class='redStar'>*</span>");
  66. $("form[name='SearchIndexes'] td.main_text:contains('First Forename:')").text("Forename 1:");
  67. $("form[name='SearchIndexes'] td.main_text:contains('Second Forename:')").text("Forename 2:");
  68. $("form[name='SearchIndexes'] td.main_text:contains('Maiden Surname:')").text("Mother:");
  69. $("form[name='SearchIndexes'] td.main_text:contains('District of Birth:')").text("District:");
  70.  
  71. // Add gender and year navigation buttons, and style them
  72. var searchButton = $("form[name='SearchIndexes'] input[type='submit'][value='Search']");
  73. $(searchButton).attr("accesskey", "?");
  74. $(searchButton).parent().find("br").remove();
  75.  
  76. $("<input type='button' class='formButton' accesskey='#' id='groish_BtnToggleGender' value='Gender' />").insertBefore($(searchButton));
  77. $("<input type='button' class='formButton' accesskey='[' id='groish_BtnYearsPrev' value='&lt; Years' />").insertBefore($(searchButton));
  78. $("<input type='button' class='formButton' accesskey=']' id='groish_BtnYearsNext' value='Years &gt;' />").insertBefore($(searchButton));
  79. var buttonContainer = $("form[name='SearchIndexes'] input[type='submit'][value='Search']").closest("td").addClass("groish_ButtonContainer");
  80. // Add button event handlers
  81. $("input#groish_BtnYearsPrev").click(function() { navigateYears(false); });
  82. $("input#groish_BtnYearsNext").click(function() { navigateYears(true); });
  83. $("input#groish_BtnToggleGender").click(function() { toggleGender(); });
  84.  
  85. // Set encoding
  86. if (typeof $("form[name='SearchIndexes']").attr("accept-charset") === typeof undefined) {
  87. $("form[name='SearchIndexes']").attr("accept-charset", "UTF-8");
  88. }
  89.  
  90. }
  91. var initialiseResultViews = function() {
  92. // Move default results table into a view container
  93. var defaultTable = $("form[name='SearchIndexes'] h3:contains('Results:')").closest("table").css("width", "100%").addClass("groish_ResultsTable");
  94. $(defaultTable).before($("<div results-view='default' />"));
  95. var defaultView = $("div[results-view='default']");
  96. $(defaultView).append($("table.groish_ResultsTable"));
  97.  
  98. // Move header row to before default view
  99. $(defaultView).before($("<div class='groish_ResultsHeader' style='margin: 10px 0px; position: relative' />"));
  100. $(".groish_ResultsHeader").append($("table.groish_ResultsTable h3:contains('Results:')"));
  101.  
  102. // Move pager row contents to after default view
  103. $(defaultView).after($("table.groish_ResultsTable > tbody > tr:last table:first"));
  104. $("div[results-view='default'] + table").css("width", "100%").addClass("groish_ResultsInfo");
  105.  
  106. // Get results, sort them and populate views
  107. results = getResults(recordType);
  108. sortResults();
  109. populateAlternateViews();
  110. }
  111. var sortResults = function(reverse, sortFieldsCsv) {
  112. //console.log("sorting results, sort fields: %s", sortFieldsCsv);
  113. if (!results || !results.items)
  114. return;
  115. var defaultSortFields = "year,quarter";
  116. // Get the last sort fields and order for the record type
  117. var sortFieldsKey = recordType + "-sort-fields";
  118. var sortOrderKey = recordType + "-sort-order";
  119. var lastSortFields = sessionStorage.getItem(sortFieldsKey);
  120. var lastSortOrder = sessionStorage.getItem(sortOrderKey);
  121. // Cleanup values
  122. sortFieldsCsv = (sortFieldsCsv || "").replace(/\s\s+/g, ' ');
  123. lastSortFields = (lastSortFields || "").replace(/\s\s+/g, ' ');
  124. //console.log("last sort fields: %s; last sort order: %s", lastSortFields, lastSortOrder);
  125. var sortOrder = "asc";
  126. if (!sortFieldsCsv) {
  127. sortFieldsCsv = lastSortFields || defaultSortFields;
  128. sortOrder = lastSortOrder || "asc";
  129. }
  130. else if (sortFieldsCsv.localeCompare(lastSortFields) == 0 && sortOrder.localeCompare(lastSortOrder) == 0 && reverse) {
  131. sortOrder = "desc";
  132. }
  133. // Build sort fields and order arrays
  134. var sortFields = sortFieldsCsv.split(",");
  135. var sortOrders = Array.apply(null, Array(sortFields.length)).map(String.prototype.valueOf, sortOrder);
  136. // Append defaults if needed
  137. if (sortFieldsCsv.localeCompare(defaultSortFields) != 0) {
  138. sortFields.push("year");
  139. sortFields.push("quarter");
  140. sortOrders.push("asc");
  141. sortOrders.push("asc");
  142. }
  143. //console.log("sorting results by: %s (%s)", sortFields, sortOrders);
  144. results.items = _.orderBy(results.items, sortFields, sortOrders);
  145. sessionStorage.setItem(sortFieldsKey, sortFieldsCsv);
  146. sessionStorage.setItem(sortOrderKey, sortOrder);
  147. }
  148.  
  149. var populateAlternateViews = function() {
  150. // Add alternate view(s)
  151. if (recordType && resources && results && results.items && results.items.length > 0) {
  152. // Remove any existing views
  153. $("div[results-view][results-view!='default']").remove();
  154. // Add alternate views
  155. //console.log("Adding alternate views...");
  156. var viewPrefix = "view_" + recordType; // record type = EW_Birth, EW_Death
  157. for (var resourceName in resources) {
  158. var resourceNamePrefix = resourceName.substring(0, viewPrefix.length);
  159. if (resources.hasOwnProperty(resourceName) && viewPrefix.localeCompare(resourceNamePrefix) == 0) {
  160. var template = resources[resourceName].toString();
  161. var compiledTemplate = Handlebars.compile(template);
  162. var html = compiledTemplate(results);
  163. if (html)
  164. $("div[results-view]").filter(":last").after($(html));
  165. }
  166. }
  167. // Add view helpers and event handlers, if not already added
  168. if ($("div[results-view]").length > 1) {
  169. // Add event handler to hide/show actions row
  170. // TODO: Make adding view event handlers more dynamic, so they can be specific to the view
  171. $("div[results-view][results-view!='default'] tbody tr.rec")
  172. .off("click.groish")
  173. .on("click.groish", function(event) {
  174.  
  175. event.preventDefault();
  176. $(this).next("tr.rec-actions:not(:empty)").toggle();
  177. }
  178. );
  179.  
  180. // Add event handler for column sorting
  181. $("div[results-view][results-view!='default'] thead td[sort-fields]")
  182. .off("click.groish")
  183. .on("click.groish", function(event) {
  184.  
  185. event.preventDefault();
  186. //var defaultSortFields = ($(this).closet("div[results-view]").attr("default-sort-fields");
  187. var sortFields = ($(this).attr("sort-fields") ? $(this).attr("sort-fields") : $(this).text());
  188. sortResults(true, sortFields);
  189. populateAlternateViews();
  190. }
  191. );
  192.  
  193. // Add view switcher, if it doesn't already exist
  194. if ($("#groish_ViewSwitcher").length == 0) {
  195. $(".groish_ResultsHeader").append($("<a href='#' id='groish_ViewSwitcher' class='main_text' accesskey='~'>Switch view</a>"));
  196. $("#groish_ViewSwitcher").off("click.groish").on("click.groish", function() { switchResultsView(); return false; });
  197.  
  198.  
  199. // Add results copier (if supported)
  200. if (window.getSelection && document.createRange) {
  201. $(".groish_ResultsHeader").append($("<a href='#' id='groish_ResultsCopier' class='main_text' accesskey='|'>Copy results</a>"));
  202. $("#groish_ResultsCopier")
  203. .off("click.groish")
  204. .on("click.groish", function(event) {
  205.  
  206. event.preventDefault();
  207.  
  208. // Get most specific element containing results, typically a table body
  209. var resultsContent = $("div[results-view]:visible tbody");
  210.  
  211. if (resultsContent.length == 0)
  212. resultsContent = $("div[results-view]:visible");
  213. if (resultsContent.length > 0) {
  214. resultsContent = resultsContent[0];
  215. var selection = window.getSelection();
  216. var range = document.createRange();
  217. range.selectNodeContents(resultsContent);
  218. selection.removeAllRanges();
  219. selection.addRange(range);
  220.  
  221. try {
  222. if (document.execCommand("copy")) {
  223. selection.removeAllRanges();
  224. $(".groish_Message").text("Results copied to clipboard").show();
  225. setTimeout(function() { $(".groish_Message").fadeOut(); }, 3000);
  226. }
  227. }
  228. catch { }
  229. }
  230.  
  231. return false;
  232. });
  233. }
  234. }
  235. }
  236.  
  237. // Show the last used view
  238. var viewName = sessionStorage.getItem("groish_view." + recordType);
  239. //console.log("initialising view: %s", viewName);
  240. if (viewName && $("div[results-view='" + viewName + "']:hidden").length == 1) {
  241. //console.log("setting active view: %s", viewName);
  242. $("div[results-view][results-view!='" + viewName + "']").hide();
  243. $("div[results-view][results-view='" + viewName + "']").show();
  244. }
  245. }
  246. }
  247.  
  248. var switchResultsView = function() {
  249. var views = $("div[results-view]");
  250. if (views.length > 1) {
  251. var curIndex = -1;
  252. $(views).each(function(index) {
  253. if ($(this).css("display") != "none")
  254. curIndex = index;
  255. });
  256.  
  257. //console.log("current view index: %s", curIndex);
  258. if (curIndex !== -1) {
  259. var newIndex = ((curIndex == (views.length-1)) ? 0 : curIndex+1);
  260. $(views).hide();
  261. $("div[results-view]:eq(" + newIndex + ")").show();
  262.  
  263. $(".groish_Message").hide();
  264.  
  265. // Get the name and save it
  266. var viewName = $("div[results-view]:eq(" + newIndex + ")").attr("results-view")
  267. sessionStorage.setItem("groish_view." + recordType, viewName); //save it
  268. //console.log("new view: %s", viewName);
  269. }
  270. }
  271. }
  272. var getResults = function(recordType) {
  273. var results = { "ageWarningThreshold": 24, "items": [], "failures": [] };
  274. // Lookup record type - birth or death
  275. if (recordType !== null && (recordType === "EW_Birth" || recordType === "EW_Death")) {
  276. var gender = $("form[name='SearchIndexes'] select#Gender").val();
  277. $("div[results-view='default'] > table > tbody > tr")
  278. .has("img[src='./graphics/order_certificate_button.gif'],img[src='./graphics/order_button.gif']")
  279. .each(function(index) {
  280. try
  281. {
  282. //console.log("Parsing record (%d)...", index);
  283. // Get names and reference
  284. var names = $(this).find("td:eq(0)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ').trim();
  285. var ref = $(this).next().find("td:eq(0)").text();
  286.  
  287. // Clean up reference
  288. ref = ref.replace(/\u00a0/g, " ");
  289. ref = ref.replace(/\s\s+/g, ' ');
  290. ref = ref.replace(/GRO Reference: /g, "");
  291. ref = ref.replace(/M Quarter in/g, "Q1");
  292. ref = ref.replace(/J Quarter in/g, "Q2");
  293. ref = ref.replace(/S Quarter in/g, "Q3");
  294. ref = ref.replace(/D Quarter in/g, "Q4");
  295.  
  296. var age = 0;
  297. if (recordType === "EW_Death") {
  298. var ageArr = /^([0-9]{1,3})$/.exec($(this).find("td:eq(1)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ').trim());
  299. if (ageArr)
  300. age = parseInt(ageArr[1], 10);
  301. }
  302.  
  303. var mother = null;
  304. if (recordType === "EW_Birth")
  305. mother = toTitleCase($(this).find("td:eq(1)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ')).trim();
  306.  
  307. var actions = [];
  308. var orderCertUrl = $(this).find("a[href^='indexes_order.asp']:eq(0)").prop("href");
  309. var orderPdfUrl = $(this).next().find("a[href^='indexes_order.asp']:eq(0)").prop("href");
  310.  
  311. if (orderCertUrl) actions.push( {"text": "Order Certificate", "url": orderCertUrl });
  312. if (orderPdfUrl) actions.push( {"text": "Order Research Copy", "title": "PDF", "url": orderPdfUrl });
  313.  
  314. // Parse forenames, surname, year, quarter, district, vol, page
  315. var namesArr = /([a-z' -]+),([a-z' -]*)/gi.exec(names);
  316. var refArr = /([0-9]{4}) Q([1-4]) ([a-z\.\-,\(\)0-9\&'/ ]*)Volume ([a-z0-9]+)( Page ([0-9a-z]+)|)( Occasional Copy: ([0-9a-z]+)|)/gi.exec(ref); // NB: the district may not be set in some cases
  317.  
  318. //console.log("index: %d, namesArr: %s, refArr: %s", index, namesArr, refArr);
  319.  
  320. var quarterNames = [ "Mar", "Jun", "Sep", "Dec" ];
  321.  
  322. var record =
  323. {
  324. "gender": gender,
  325. "forenames": toTitleCase(namesArr[2]).trim(),
  326. "surname": toTitleCase(namesArr[1]).trim(),
  327. "age": age,
  328. "mother": mother,
  329. "year": parseInt(refArr[1], 10),
  330. "quarter": parseInt(refArr[2], 10),
  331. "district": toTitleCase(refArr[3]).trim(),
  332. "volume": refArr[4].toLowerCase(),
  333. "page": refArr[6],
  334. "copy": (refArr[8] ? refArr[8].trim() : ""),
  335. "actions": actions
  336. };
  337. record.noForenames = (!record.forenames || record.forenames == "-");
  338. record.ageWarning = (age != null && age > 0 && age <= results.ageWarningThreshold);
  339. record.birth = (age != null ? record.year - age : null);
  340. record.quarterName = ((record.quarter >=1 && record.quarter <= 4) ? quarterNames[record.quarter-1] : null);
  341. //console.log(record);
  342. results.items.push(record);
  343. }
  344. catch (e)
  345. {
  346. //console.log("Failed to parse record (%d): %s", index, e.message);
  347. results.failures.push({ "index": index, "ex": e });
  348. }
  349. });
  350. }
  351. return results;
  352. }
  353.  
  354.  
  355. var toTitleCase = function(str) {
  356. return str.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
  357. }
  358. var switchRecordType = function() {
  359. var recordTypes = $("form[name='SearchIndexes'] input[type='Radio'][name='index']");
  360.  
  361. var curIndex = -1;
  362. for (var i = 0; i < recordTypes.length; i++) {
  363. if ($(recordTypes).eq(i).prop("checked")) {
  364. curIndex = i;
  365. break;
  366. }
  367. }
  368. //console.log("current record type: %d", curIndex);
  369.  
  370. if (curIndex >= 0) {
  371. var nextIndex = (curIndex == (recordTypes.length-1)) ? 0 : curIndex + 1;
  372.  
  373. if (nextIndex != curIndex)
  374. $(recordTypes).eq(nextIndex).prop("checked", true).click();
  375. //console.log("next record type: %d", nextIndex);
  376. }
  377. }
  378.  
  379. var toggleGender = function() {
  380. var curGender = $("form[name='SearchIndexes'] select#Gender").val();
  381. $("form[name='SearchIndexes'] select#Gender").val((curGender === "F" ? "M" : "F"));
  382. $("form[name='SearchIndexes'] input[type='submit'][value='Search']").click();
  383. }
  384. var adjustSearchYear = function(step) {
  385. var adjusted = false;
  386. // Get min and max years
  387. var minYear = parseInt($("form[name='SearchIndexes'] select#Year option:eq(2)").val(), 10);
  388. var maxYear = parseInt($("form[name='SearchIndexes'] select#Year option:last").val(), 10);
  389.  
  390. //console.log("Year range: %s - %s", minYear, maxYear);
  391.  
  392. if (!isNaN(step) && !isNaN(minYear) && !isNaN(maxYear)) {
  393. // Read current year and range
  394. var curYear = parseInt($("form[name='SearchIndexes'] select#Year").val(), 10);
  395. var curRange = parseInt($("form[name='SearchIndexes'] select#Range").val(), 10);
  396.  
  397. if (!isNaN(curRange)) {
  398. // Calculate the new year
  399. var newYear = (isNaN(curYear) ? minYear : curYear+step);
  400. newYear = Math.min(Math.max(newYear, minYear), maxYear);
  401. if (newYear != curYear) {
  402. $("form[name='SearchIndexes'] select#Year").val(newYear);
  403. adjusted = true;
  404. }
  405. }
  406.  
  407. //console.log("Current year: %d +-%d (%d-%d), New year: %d (%d-%d)", curYear, curRange, curYear-curRange, curYear+curRange, newYear, newYear-curRange, newYear+curRange);
  408. }
  409.  
  410. return adjusted;
  411. }
  412.  
  413. var navigateYears = function(forward) {
  414. var curRange = parseInt($("form[name='SearchIndexes'] select#Range").val(), 10);
  415. if (!isNaN(curRange)) {
  416. // Calculate the new year
  417. var step = (curRange * 2) + 1;
  418. if (!forward) step = -step;
  419. if (adjustSearchYear(step)) {
  420. $("form[name='SearchIndexes'] input[type='submit'][value='Search']").click();
  421. }
  422. }
  423. }
  424. var getRecordType = function() {
  425. return $("form[name='SearchIndexes'] input[type='radio'][name='index']:checked").val();
  426. }
  427.  
  428.  
  429. var buildResources = function() {
  430. resources = {
  431.  
  432. baseStyle: `
  433. <style type="text/css">
  434. body
  435. {
  436. min-height: 1200px;
  437. }
  438.  
  439. form[name="SearchIndexes"]
  440. {
  441. position: relative !important;
  442. }
  443. .groish_ButtonContainer
  444. {
  445. padding-bottom: 10px;
  446. }
  447. .groish_ButtonContainer input[type='submit'],
  448. .groish_ButtonContainer input[type='button']
  449. {
  450. margin-right: 20px;
  451. min-width: 100px;
  452. font-size: 13px;
  453. padding: 4px 10px;
  454. }
  455. .groish_ButtonContainer input[type='submit']
  456. {
  457. margin-right: 0px;
  458. }
  459. #groish_ResultsCopier,
  460. #groish_ViewSwitcher
  461. {
  462. display:inline-block;
  463. position: absolute;
  464. bottom: 0px;
  465. color: #993333;
  466. font-weight: bold;
  467. cursor: pointer;
  468. }
  469. #groish_ResultsCopier
  470. {
  471. right: 120px;
  472. }
  473. #groish_ViewSwitcher
  474. {
  475. right: 10px;
  476. }
  477. div[results-view] td[sort-fields]:hover
  478. {
  479. cursor: pointer;
  480. }
  481.  
  482. .groish_Message
  483. {
  484. position: absolute;
  485. bottom: -30px;
  486. left: 5px;
  487. }
  488.  
  489. </style>
  490. `,
  491.  
  492. view_EW_Birth_Table: `
  493. <style type="text/css">
  494. div[results-view='EW_Birth-Table'] td
  495. {
  496. padding: 5px 3px;
  497. font-size: 75%;
  498. color: #663333;
  499. vertical-align: top;
  500. }
  501. div[results-view='EW_Birth-Table'] thead td
  502. {
  503. font-weight: bold;
  504. }
  505. div[results-view='EW_Birth-Table'] tbody tr:nth-child(4n+1),
  506. div[results-view='EW_Birth-Table'] tbody tr:nth-child(4n+2)
  507. {
  508. background-color: #F9E8A5;
  509. }
  510. div[results-view='EW_Birth-Table'] tr.rec-actions a
  511. {
  512. padding: 0px 5px;
  513. font-size: 90%;
  514. color: #663333;
  515. text-decoration: none;
  516. }
  517. </style>
  518. <div results-view='EW_Birth-Table' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'>
  519. <table style='width: 100%; border-collapse: collapse'>
  520. <thead>
  521. <tr>
  522. <td style='width: 12%' sort-fields='year,quarter'>Date</td>
  523. <td style='width: 30%' sort-fields='forenames,surname'>Name</td>
  524. <td style='width: 15%' sort-fields='mother'>Mother</td>
  525. <td style='width: 25%' sort-fields='district'>District</td>
  526. <td style='width: 6%' sort-fields='volume,district'>Vol</td>
  527. <td style='width: 6%' sort-fields='page,volume'>Page</td>
  528. <td style='width: 6%' sort-fields='copy,volume'>Copy</td>
  529. </tr>
  530. </thead>
  531. <tbody>
  532. {{#each items}}
  533. <tr class='rec'>
  534. <td>{{year}} Q{{quarter}}</td>
  535. <td><span class='forenames'>{{forenames}}</span> <span class='surname'>{{surname}}</span>{{#if noForenames}} ({{gender}}){{/if}}</td>
  536. <td>{{mother}}</td>
  537. <td>{{district}}</td>
  538. <td>{{volume}}</td>
  539. <td>{{page}}</td>
  540. <td>{{copy}}</td>
  541. </tr>
  542. <tr class='rec-actions' style='display: none'>
  543. <td colspan='7' style='text-align: right'>
  544. {{#actions}}
  545. <a href='{{url}}' {{#if title}}title='{{title}}'{{/if}}>{{text}}</a>
  546. {{/actions}}
  547. </td>
  548. </tr>
  549. {{/each}}
  550. </tbody>
  551. </table>
  552. {{#if failures}}
  553. <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p>
  554. <!--
  555. {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}}
  556. -->
  557. {{/if}}
  558. <p class='main_text groish_Message'></p>
  559. </div>`,
  560. view_EW_Birth_Delimited: `
  561. <style type="text/css">
  562. div[results-view='EW_Birth-Delimited'] td
  563. {
  564. padding: 5px 3px;
  565. font-size: 75%;
  566. color: #663333;
  567. vertical-align: top;
  568. }
  569. div[results-view='EW_Birth-Delimited'] thead td
  570. {
  571. font-weight: bold;
  572. }
  573. div[results-view='EW_Birth-Delimited'] tbody tr:nth-child(odd)
  574. {
  575. background-color: #F9E8A5;
  576. }
  577.  
  578. </style>
  579. <div results-view='EW_Birth-Delimited' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'>
  580. <table style='width: 100%; border-collapse: collapse'>
  581. <thead>
  582. <tr>
  583. <td style='width: 100%' sort-fields='year,quarter'>Births</td>
  584. </tr>
  585. </thead>
  586. <tbody>
  587. {{#each items}}
  588. <tr class='rec'>
  589. <td>
  590. {{year}} Q{{quarter}} Birth -
  591. {{forenames}} {{surname}}{{#if noForenames}} ({{gender}}){{/if}}
  592. (mmn: {{mother}});
  593. {{district}}; {{volume}}; {{page}}; {{copy}}
  594. </tr>
  595. {{/each}}
  596. </tbody>
  597. </table>
  598. {{#if failures}}
  599. <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p>
  600. <!--
  601. {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}}
  602. -->
  603. {{/if}}
  604. <p class='main_text groish_Message'></p>
  605. </div>`,
  606.  
  607. view_EW_Death_Table: `
  608. <style type="text/css">
  609. div[results-view='EW_Death-Table'] td
  610. {
  611. padding: 5px 3px;
  612. font-size: 75%;
  613. color: #663333;
  614. vertical-align: top;
  615. }
  616. div[results-view='EW_Death-Table'] thead td
  617. {
  618. font-weight: bold;
  619. }
  620. div[results-view='EW_Death-Table'] tbody tr:nth-child(4n+1),
  621. div[results-view='EW_Death-Table'] tbody tr:nth-child(4n+2)
  622. {
  623. background-color: #F9E8A5;
  624. }
  625. div[results-view='EW_Death-Table'] tr.rec-actions a
  626. {
  627. padding: 0px 5px;
  628. font-size: 90%;
  629. color: #663333;
  630. text-decoration: none;
  631. }
  632. </style>
  633. <div results-view='EW_Death-Table' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'>
  634. <table style='width: 100%; border-collapse: collapse'>
  635. <thead>
  636. <tr>
  637. <td style='width: 12%' sort-fields='year,quarter'>Date</td>
  638. <td style='width: 26%' sort-fields='forenames,surname'>Name</td>
  639. <td style='width: 8%' sort-fields='age'>Age{{#if ageCautionThreshold}}*{{/if}}</td>
  640. <td style='width: 8%' sort-fields='birth'>Birth</td>
  641. <td style='width: 28%' sort-fields='district'>District</td>
  642. <td style='width: 6%' sort-fields='volume,district'>Vol</td>
  643. <td style='width: 6%' sort-fields='page,volume'>Page</td>
  644. <td style='width: 6%' sort-fields='copy,volume'>Copy</td>
  645. </tr>
  646. </thead>
  647. <tbody>
  648. {{#each items}}
  649. <tr class='rec'>
  650. <td>{{year}} Q{{quarter}}</td>
  651. <td><span class='forenames'>{{forenames}}</span> <span class='surname'>{{surname}}</span>{{#if noForenames}} ({{gender}}){{/if}}</td>
  652. <td>{{age}}{{#if ageWarning}}*{{/if}}</td>
  653. <td>{{birth}}
  654. <td>{{district}}</td>
  655. <td>{{volume}}</td>
  656. <td>{{page}}</td>
  657. <td>{{copy}}</td>
  658. </tr>
  659. <tr class='rec-actions' style='display: none'>
  660. <td colspan='8' style='text-align: right'>
  661. {{#actions}}
  662. <a href='{{url}}' {{#if title}}title='{{title}}'{{/if}}>{{text}}</a>
  663. {{/actions}}
  664. </td>
  665. </tr>
  666. {{/each}}
  667. </tbody>
  668. </table>
  669. {{#if failures}}
  670. <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p>
  671. <!--
  672. {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}}
  673. -->
  674. {{/if}}
  675. <p class='main_text'>
  676. * Age is presumed to be years but <i>may</i> be months.
  677. {{#if ageWarningThreshold}}An age below {{ageWarningThreshold}} <i>may</i> be a child, treat with caution.{{/if}}
  678. An age of zero <i>may</i> have be used when a child was aged less than 12 months.
  679. </p>
  680. <p class='main_text groish_Message'></p>
  681. </div>`,
  682.  
  683. view_EW_Death_Delimited: `
  684. <style type="text/css">
  685. div[results-view='EW_Death-Delimited'] td
  686. {
  687. padding: 5px 3px;
  688. font-size: 75%;
  689. color: #663333;
  690. vertical-align: top;
  691. }
  692. div[results-view='EW_Death-Delimited'] thead td
  693. {
  694. font-weight: bold;
  695. }
  696. div[results-view='EW_Death-Delimited'] tbody tr:nth-child(odd)
  697. {
  698. background-color: #F9E8A5;
  699. }
  700. </style>
  701. <div results-view='EW_Death-Delimited' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'>
  702. <table style='width: 100%; border-collapse: collapse'>
  703. <thead>
  704. <tr>
  705. <td style='width: 100%' sort-fields='year,quarter'>Deaths</td>
  706. </tr>
  707. </thead>
  708. <tbody>
  709. {{#each items}}
  710. <tr class='rec'>
  711. <td>
  712. {{year}} Q{{quarter}} Death -
  713. {{forenames}} {{surname}}{{#if noForenames}} ({{gender}}){{/if}};
  714. age {{age}}{{#if ageWarning}}*{{/if}} (b{{birth}});
  715. {{district}}; {{volume}}; {{page}}; {{copy}}
  716. </td>
  717. </tr>
  718. {{/each}}
  719. </tbody>
  720. </table>
  721. {{#if failures}}
  722. <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p>
  723. <!--
  724. {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}}
  725. -->
  726. {{/if}}
  727. <p class='main_text'>
  728. * Age is presumed to be years but <i>may</i> be months.
  729. {{#if ageWarningThreshold}}An age below {{ageWarningThreshold}} <i>may</i> be a child, treat with caution.{{/if}}
  730. An age of zero <i>may</i> have be used when a child was aged less than 12 months.
  731. </p>
  732. <p class='main_text groish_Message'></p>
  733. </div>`
  734.  
  735. };
  736.  
  737. // Add custom views
  738. // NB: Although GreaseMonkey has replaced the GM_* functions with functions on the GM object
  739. // both ViolentMonkey and TamperMonkey still support the GM_* functions so that's being used.
  740.  
  741. // Custom views are defined as GM values (one value per view). The value name must begin with
  742. // either view_EW_Birth or view_EW_Death. The view may contain CSS and HTML and the views are
  743. // Handlebars.js templates (see default views above for examples).
  744. //console.log("adding custom views");
  745. if (typeof GM_listValues === "function" && typeof GM_getValue === "function") {
  746. var valueKeys = GM_listValues();
  747. for(var i = 0; i < valueKeys.length; i++) {
  748. var valueKey = valueKeys[i];
  749. //console.log("value key: %", valueKey);
  750. if (valueKey && valueKey.length > 13 && (valueKey.startsWith("view_EW_Birth") || valueKey.startsWith("view_EW_Death"))) {
  751. // Check the key isn't already in use
  752. if (!resources.hasOwnProperty(valueKey)) {
  753. var viewContent = GM_getValue(valueKey, null);
  754. if (viewContent) {
  755. //console.log("adding view: %s", valueKey);
  756. resources[valueKey] = viewContent;
  757. }
  758. }
  759. }
  760. }
  761. }
  762. }
  763.  
  764. //Get the ball rolling...
  765. main();
  766. });