public static function familyTable($families)
{
global $WT_TREE, $controller;
$table_id = 'table-fam-' . Uuid::uuid4();
// lists requires a unique ID in case there are multiple lists per page
$controller->addExternalJavascript(WT_JQUERY_DATATABLES_JS_URL)->addInlineJavascript('
jQuery.fn.dataTableExt.oSort["text-asc"] = textCompareAsc;
jQuery.fn.dataTableExt.oSort["text-desc"] = textCompareDesc;
jQuery("#' . $table_id . '").dataTable( {
dom: \'<"H"<"filtersH_' . $table_id . '"><"dt-clear">pf<"dt-clear">irl>t<"F"pl<"dt-clear"><"filtersF_' . $table_id . '">>\',
' . I18N::datatablesI18N() . ',
jQueryUI: true,
autoWidth: false,
processing: true,
retrieve: true,
columns: [
/* Given names */ { type: "text" },
/* Surnames */ { type: "text" },
/* Age */ { type: "num" },
/* Given names */ { type: "text" },
/* Surnames */ { type: "text" },
/* Age */ { type: "num" },
/* Marriage date */ { type: "num" },
/* Anniversary */ { type: "num" },
/* Marriage place */ { type: "text" },
/* Children */ { type: "num" },
/* Last change */ { visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
/* Filter marriage */ { sortable: false },
/* Filter alive/dead */ { sortable: false },
/* Filter tree */ { sortable: false }
],
sorting: [[1, "asc"]],
displayLength: 20,
pagingType: "full_numbers"
});
jQuery("#' . $table_id . '")
/* Hide/show parents */
.on("click", ".btn-toggle-parents", function() {
jQuery(this).toggleClass("ui-state-active");
jQuery(".parents", jQuery(this).closest("table").DataTable().rows().nodes()).slideToggle();
})
/* Hide/show statistics */
.on("click", ".btn-toggle-statistics", function() {
jQuery(this).toggleClass("ui-state-active");
jQuery("#fam_list_table-charts_' . $table_id . '").slideToggle();
})
/* Filter buttons in table header */
.on("click", "button[data-filter-column]", function() {
var btn = $(this);
// De-activate the other buttons in this button group
btn.siblings().removeClass("ui-state-active");
// Apply (or clear) this filter
var col = jQuery("#' . $table_id . '").DataTable().column(btn.data("filter-column"));
if (btn.hasClass("ui-state-active")) {
btn.removeClass("ui-state-active");
col.search("").draw();
} else {
btn.addClass("ui-state-active");
col.search(btn.data("filter-value")).draw();
}
});
jQuery(".fam-list").css("visibility", "visible");
jQuery(".loading-image").css("display", "none");
');
$stats = new Stats($WT_TREE);
$max_age = max($stats->oldestMarriageMaleAge(), $stats->oldestMarriageFemaleAge()) + 1;
// init chart data
$marr_by_age = array();
for ($age = 0; $age <= $max_age; $age++) {
$marr_by_age[$age] = '';
}
$birt_by_decade = array();
$marr_by_decade = array();
for ($year = 1550; $year < 2030; $year += 10) {
$birt_by_decade[$year] = '';
$marr_by_decade[$year] = '';
}
$html = '
<div class="loading-image"></div>
<div class="fam-list">
<table id="' . $table_id . '">
<thead>
<tr>
<th colspan="14">
<div class="btn-toolbar">
<div class="btn-group">
<button
type="button"
data-filter-column="12"
data-filter-value="N"
class="ui-state-default"
title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
>
' . I18N::translate('Both alive') . '
</button>
<button
type="button"
data-filter-column="12"
data-filter-value="W"
class="ui-state-default"
title="' . I18N::translate('Show couples where only the female partner is dead.') . '"
>
' . I18N::translate('Widower') . '
</button>
<button
type="button"
data-filter-column="12"
data-filter-value="H"
class="ui-state-default"
title="' . I18N::translate('Show couples where only the male partner is dead.') . '"
>
' . I18N::translate('Widow') . '
</button>
<button
type="button"
data-filter-column="12"
data-filter-value="Y"
class="ui-state-default"
title="' . I18N::translate('Show individuals who are dead or couples where both partners are dead.') . '"
>
' . I18N::translate('Both dead') . '
</button>
</div>
<div class="btn-group">
<button
type="button"
data-filter-column="13"
data-filter-value="R"
class="ui-state-default"
title="' . I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') . '"
>
' . I18N::translate('Roots') . '
</button>
<button
type="button"
data-filter-column="13"
data-filter-value="L"
class="ui-state-default"
title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
>
' . I18N::translate('Leaves') . '
</button>
</div>
<div class="btn-group">
<button
type="button"
data-filter-column="11"
data-filter-value="U"
class="ui-state-default"
title="' . I18N::translate('Show couples with an unknown marriage date.') . '"
>
' . GedcomTag::getLabel('MARR') . '
</button>
<button
type="button"
data-filter-column="11"
data-filter-value="YES"
class="ui-state-default"
title="' . I18N::translate('Show couples who married more than 100 years ago.') . '"
>
' . GedcomTag::getLabel('MARR') . '>100
</button>
<button
type="button"
data-filter-column="11"
data-filter-value="Y100"
class="ui-state-default"
title="' . I18N::translate('Show couples who married within the last 100 years.') . '"
>
' . GedcomTag::getLabel('MARR') . '<=100
</button>
<button
type="button"
data-filter-column="11"
data-filter-value="D"
class="ui-state-default"
title="' . I18N::translate('Show divorced couples.') . '"
>
' . GedcomTag::getLabel('DIV') . '
</button>
<button
type="button"
data-filter-column="11"
data-filter-value="M"
class="ui-state-default"
title="' . I18N::translate('Show couples where either partner married more than once.') . '"
>
' . I18N::translate('Multiple marriages') . '
</button>
</div>
</div>
</th>
</tr>
<tr>
<th>' . GedcomTag::getLabel('GIVN') . '</th>
<th>' . GedcomTag::getLabel('SURN') . '</th>
<th>' . GedcomTag::getLabel('AGE') . '</th>
<th>' . GedcomTag::getLabel('GIVN') . '</th>
<th>' . GedcomTag::getLabel('SURN') . '</th>
<th>' . GedcomTag::getLabel('AGE') . '</th>
<th>' . GedcomTag::getLabel('MARR') . '</th>
<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
<th>' . GedcomTag::getLabel('PLAC') . '</th>
<th><i class="icon-children" title="' . I18N::translate('Children') . '"></i></th>
<th>' . GedcomTag::getLabel('CHAN') . '</th>
<th hidden></th>
<th hidden></th>
<th hidden></th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="14">
<div class="btn-toolbar">
<div class="btn-group">
<button type="button" class="ui-state-default btn-toggle-parents">
' . I18N::translate('Show parents') . '
</button>
<button type="button" class="ui-state-default btn-toggle-statistics">
' . I18N::translate('Show statistics charts') . '
</button>
</div>
</div>
</th>
</tr>
</tfoot>
<tbody>';
$hundred_years_ago = new Date(date('Y') - 100);
foreach ($families as $family) {
// Retrieve husband and wife
$husb = $family->getHusband();
if (is_null($husb)) {
$husb = new Individual('H', '0 @H@ INDI', null, $family->getTree());
}
$wife = $family->getWife();
if (is_null($wife)) {
$wife = new Individual('W', '0 @W@ INDI', null, $family->getTree());
}
if (!$family->canShow()) {
continue;
}
if ($family->isPendingAddtion()) {
$class = ' class="new"';
} elseif ($family->isPendingDeletion()) {
$class = ' class="old"';
} else {
$class = '';
}
$html .= '<tr' . $class . '>';
// Husband name(s)
// Extract Given names and Surnames for sorting
list($surn_givn, $givn_surn) = self::sortableNames($husb);
$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
foreach ($husb->getAllNames() as $num => $name) {
if ($name['type'] == 'NAME') {
$title = '';
} else {
$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $husb)) . '"';
}
if ($num == $husb->getPrimaryName()) {
$class = ' class="name2"';
$sex_image = $husb->getSexImage();
} else {
$class = '';
$sex_image = '';
}
// Only show married names if they are the name we are filtering by.
if ($name['type'] != '_MARNM' || $num == $husb->getPrimaryName()) {
$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
}
}
// Husband parents
$html .= $husb->getPrimaryParentsNames('parents details1', 'none');
$html .= '</td>';
// Hidden column for sortable name
$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
// Husband age
$mdate = $family->getMarriageDate();
$hdate = $husb->getBirthDate();
if ($hdate->isOK() && $mdate->isOK()) {
if ($hdate->gregorianYear() >= 1550 && $hdate->gregorianYear() < 2030) {
$birt_by_decade[(int) ($hdate->gregorianYear() / 10) * 10] .= $husb->getSex();
}
$hage = Date::getAge($hdate, $mdate, 0);
if ($hage >= 0 && $hage <= $max_age) {
$marr_by_age[$hage] .= $husb->getSex();
}
}
$html .= '<td class="center" data=-sort="' . Date::getAge($hdate, $mdate, 1) . '">' . Date::getAge($hdate, $mdate, 2) . '</td>';
// Wife name(s)
// Extract Given names and Surnames for sorting
list($surn_givn, $givn_surn) = self::sortableNames($wife);
$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
foreach ($wife->getAllNames() as $num => $name) {
if ($name['type'] == 'NAME') {
$title = '';
} else {
$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $wife)) . '"';
}
if ($num == $wife->getPrimaryName()) {
$class = ' class="name2"';
$sex_image = $wife->getSexImage();
} else {
$class = '';
$sex_image = '';
}
// Only show married names if they are the name we are filtering by.
if ($name['type'] != '_MARNM' || $num == $wife->getPrimaryName()) {
$html .= '<a ' . $title . ' href="' . $family->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
}
}
// Wife parents
$html .= $wife->getPrimaryParentsNames('parents details1', 'none');
$html .= '</td>';
// Hidden column for sortable name
$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
// Wife age
$mdate = $family->getMarriageDate();
$wdate = $wife->getBirthDate();
if ($wdate->isOK() && $mdate->isOK()) {
if ($wdate->gregorianYear() >= 1550 && $wdate->gregorianYear() < 2030) {
$birt_by_decade[(int) ($wdate->gregorianYear() / 10) * 10] .= $wife->getSex();
}
$wage = Date::getAge($wdate, $mdate, 0);
if ($wage >= 0 && $wage <= $max_age) {
$marr_by_age[$wage] .= $wife->getSex();
}
}
$html .= '<td class="center" data-sort="' . Date::getAge($wdate, $mdate, 1) . '">' . Date::getAge($wdate, $mdate, 2) . '</td>';
// Marriage date
$html .= '<td data-sort="' . $family->getMarriageDate()->julianDay() . '">';
if ($marriage_dates = $family->getAllMarriageDates()) {
foreach ($marriage_dates as $n => $marriage_date) {
if ($n) {
$html .= '<br>';
}
$html .= '<div>' . $marriage_date->display(true) . '</div>';
}
if ($marriage_dates[0]->gregorianYear() >= 1550 && $marriage_dates[0]->gregorianYear() < 2030) {
$marr_by_decade[(int) ($marriage_dates[0]->gregorianYear() / 10) * 10] .= $husb->getSex() . $wife->getSex();
}
} elseif ($family->getFacts('_NMR')) {
$html .= I18N::translate('no');
} elseif ($family->getFacts('MARR')) {
$html .= I18N::translate('yes');
} else {
$html .= ' ';
}
$html .= '</td>';
// Marriage anniversary
$html .= '<td class="center" data-sort="' . -$family->getMarriageDate()->julianDay() . '">' . Date::getAge($family->getMarriageDate(), null, 2) . '</td>';
// Marriage place
$html .= '<td>';
foreach ($family->getAllMarriagePlaces() as $n => $marriage_place) {
$tmp = new Place($marriage_place, $family->getTree());
if ($n) {
$html .= '<br>';
}
$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
}
$html .= '</td>';
// Number of children
$html .= '<td class="center" data-sort="' . $family->getNumberOfChildren() . '">' . I18N::number($family->getNumberOfChildren()) . '</td>';
// Last change
$html .= '<td data-sort="' . $family->lastChangeTimestamp(true) . '">' . $family->lastChangeTimestamp() . '</td>';
// Filter by marriage date
$html .= '<td hidden>';
if (!$family->canShow() || !$mdate->isOK()) {
$html .= 'U';
} else {
if (Date::compare($mdate, $hundred_years_ago) > 0) {
$html .= 'Y100';
} else {
$html .= 'YES';
}
}
if ($family->getFacts(WT_EVENTS_DIV)) {
$html .= 'D';
}
if (count($husb->getSpouseFamilies()) > 1 || count($wife->getSpouseFamilies()) > 1) {
$html .= 'M';
}
$html .= '</td>';
// Filter by alive/dead
$html .= '<td hidden>';
if ($husb->isDead() && $wife->isDead()) {
$html .= 'Y';
}
if ($husb->isDead() && !$wife->isDead()) {
if ($wife->getSex() == 'F') {
$html .= 'H';
}
if ($wife->getSex() == 'M') {
$html .= 'W';
}
// male partners
}
if (!$husb->isDead() && $wife->isDead()) {
if ($husb->getSex() == 'M') {
$html .= 'W';
}
if ($husb->getSex() == 'F') {
$html .= 'H';
}
// female partners
}
if (!$husb->isDead() && !$wife->isDead()) {
$html .= 'N';
}
$html .= '</td>';
// Filter by roots/leaves
$html .= '<td hidden>';
if (!$husb->getChildFamilies() && !$wife->getChildFamilies()) {
$html .= 'R';
} elseif (!$husb->isDead() && !$wife->isDead() && $family->getNumberOfChildren() === 0) {
$html .= 'L';
}
$html .= '</td>
</tr>';
}
$html .= '
</tbody>
</table>
<div id="fam_list_table-charts_' . $table_id . '" style="display:none">
<table class="list-charts">
<tr>
<td>' . self::chartByDecade($birt_by_decade, I18N::translate('Decade of birth')) . '</td>
<td>' . self::chartByDecade($marr_by_decade, I18N::translate('Decade of marriage')) . '</td>
</tr>
<tr>
<td colspan="2">' . self::chartByAge($marr_by_age, I18N::translate('Age in year of marriage')) . '</td>
</tr>
</table>
</div>
</div>';
return $html;
}