GRO Index Search Helper

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

当前为 2019-09-12 提交的版本,查看 最新版本

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