GRO Index Search Helper

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

当前为 2017-08-27 提交的版本,查看 最新版本

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