public static function individualTable($indiviudals, $option = '')
{
global $controller, $WT_TREE;
$table_id = 'table-indi-' . 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 . '">T<"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" },
/* SOSA numnber */ { type: "num", visible: ' . ($option == 'sosa' ? 'true' : 'false') . ' },
/* Birth date */ { type: "num" },
/* Anniversary */ { type: "num" },
/* Birthplace */ { type: "text" },
/* Children */ { type: "num" },
/* Deate date */ { type: "num" },
/* Anniversary */ { type: "num" },
/* Age */ { type: "num" },
/* Death place */ { type: "text" },
/* Last change */ { visible: ' . ($WT_TREE->getPreference('SHOW_LAST_CHANGE') ? 'true' : 'false') . ' },
/* Filter sex */ { sortable: false },
/* Filter birth */ { sortable: false },
/* Filter death */ { sortable: false },
/* Filter tree */ { sortable: false }
],
sorting: [[' . ($option == 'sosa' ? '4, "asc"' : '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("#indi_list_table-charts_' . $table_id . '").slideToggle();
})
/* Filter buttons in table header */
.on("click", "button[data-filter-column]", function() {
var btn = jQuery(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(".indi-list").css("visibility", "visible");
jQuery(".loading-image").css("display", "none");
');
$stats = new Stats($WT_TREE);
// Bad data can cause "longest life" to be huge, blowing memory limits
$max_age = min($WT_TREE->getPreference('MAX_ALIVE_AGE'), $stats->longestLifeAge()) + 1;
// Inititialise chart data
$deat_by_age = array();
for ($age = 0; $age <= $max_age; $age++) {
$deat_by_age[$age] = '';
}
$birt_by_decade = array();
$deat_by_decade = array();
for ($year = 1550; $year < 2030; $year += 10) {
$birt_by_decade[$year] = '';
$deat_by_decade[$year] = '';
}
$html = '
<div class="loading-image"></div>
<div class="indi-list">
<table id="' . $table_id . '">
<thead>
<tr>
<th colspan="16">
<div class="btn-toolbar">
<div class="btn-group">
<button
class="ui-state-default"
data-filter-column="12"
data-filter-value="M"
title="' . I18N::translate('Show only males.') . '"
type="button"
>
' . Individual::sexImage('M', 'large') . '
</button>
<button
class="ui-state-default"
data-filter-column="12"
data-filter-value="F"
title="' . I18N::translate('Show only females.') . '"
type="button"
>
' . Individual::sexImage('F', 'large') . '
</button>
<button
class="ui-state-default"
data-filter-column="12"
data-filter-value="U"
title="' . I18N::translate('Show only individuals for whom the gender is not known.') . '"
type="button"
>
' . Individual::sexImage('U', 'large') . '
</button>
</div>
<div class="btn-group">
<button
class="ui-state-default"
data-filter-column="14"
data-filter-value="N"
title="' . I18N::translate('Show individuals who are alive or couples where both partners are alive.') . '"
type="button"
>
' . I18N::translate('Alive') . '
</button>
<button
class="ui-state-default"
data-filter-column="14"
data-filter-value="Y"
title="' . I18N::translate('Show individuals who are dead or couples where both partners are dead.') . '"
type="button"
>
' . I18N::translate('Dead') . '
</button>
<button
class="ui-state-default"
data-filter-column="14"
data-filter-value="YES"
title="' . I18N::translate('Show individuals who died more than 100 years ago.') . '"
type="button"
>
' . GedcomTag::getLabel('DEAT') . '>100
</button>
<button
class="ui-state-default"
data-filter-column="14"
data-filter-value="Y100"
title="' . I18N::translate('Show individuals who died within the last 100 years.') . '"
type="button"
>
' . GedcomTag::getLabel('DEAT') . '<=100
</button>
</div>
<div class="btn-group">
<button
class="ui-state-default"
data-filter-column="13"
data-filter-value="YES"
title="' . I18N::translate('Show individuals born more than 100 years ago.') . '"
type="button"
>
' . GedcomTag::getLabel('BIRT') . '>100
</button>
<button
class="ui-state-default"
data-filter-column="13"
data-filter-value="Y100"
title="' . I18N::translate('Show individuals born within the last 100 years.') . '"
type="button"
>
' . GedcomTag::getLabel('BIRT') . '<=100
</button>
</div>
<div class="btn-group">
<button
class="ui-state-default"
data-filter-column="15"
data-filter-value="R"
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.') . '"
type="button"
>
' . I18N::translate('Roots') . '
</button>
<button
class="ui-state-default"
data-filter-column="15"
data-filter-value="L"
title="' . I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') . '"
type="button"
>
' . I18N::translate('Leaves') . '
</button>
</div>
</div>
</th>
</tr>
<tr>
<th>' . GedcomTag::getLabel('GIVN') . '</th>
<th>' . GedcomTag::getLabel('SURN') . '</th>
<th>' . I18N::translate('Sosa') . '</th>
<th>' . GedcomTag::getLabel('BIRT') . '</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('DEAT') . '</th>
<th><i class="icon-reminder" title="' . I18N::translate('Anniversary') . '"></i></th>
<th>' . GedcomTag::getLabel('AGE') . '</th>
<th>' . GedcomTag::getLabel('PLAC') . '</th>
<th>' . GedcomTag::getLabel('CHAN') . '</th>
<th hidden></th>
<th hidden></th>
<th hidden></th>
<th hidden></th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="16">
<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);
$unique_indis = array();
// Don't double-count indis with multiple names.
foreach ($indiviudals as $key => $individual) {
if (!$individual->canShowName()) {
continue;
}
if ($individual->isPendingAddtion()) {
$class = ' class="new"';
} elseif ($individual->isPendingDeletion()) {
$class = ' class="old"';
} else {
$class = '';
}
$html .= '<tr' . $class . '>';
// Extract Given names and Surnames for sorting
list($surn_givn, $givn_surn) = self::sortableNames($individual);
$html .= '<td colspan="2" data-sort="' . Filter::escapeHtml($givn_surn) . '">';
foreach ($individual->getAllNames() as $num => $name) {
if ($name['type'] == 'NAME') {
$title = '';
} else {
$title = 'title="' . strip_tags(GedcomTag::getLabel($name['type'], $individual)) . '"';
}
if ($num == $individual->getPrimaryName()) {
$class = ' class="name2"';
$sex_image = $individual->getSexImage();
} else {
$class = '';
$sex_image = '';
}
$html .= '<a ' . $title . ' href="' . $individual->getHtmlUrl() . '"' . $class . '>' . FunctionsPrint::highlightSearchHits($name['full']) . '</a>' . $sex_image . '<br>';
}
$html .= $individual->getPrimaryParentsNames('parents details1', 'none');
$html .= '</td>';
// Hidden column for sortable name
$html .= '<td hidden data-sort="' . Filter::escapeHtml($surn_givn) . '"></td>';
// SOSA
$html .= '<td class="center" data-sort="' . $key . '"><a href="relationship.php?pid1=' . $indiviudals[1] . '&pid2=' . $individual->getXref() . '" title="' . I18N::translate('Relationships') . '">' . I18N::number($key) . '</a></td>';
// Birth date
$birth_dates = $individual->getAllBirthDates();
$html .= '<td data-sort="' . $individual->getEstimatedBirthDate()->julianDay() . '">';
foreach ($birth_dates as $n => $birth_date) {
if ($n > 0) {
$html .= '<br>';
}
$html .= $birth_date->display(true);
}
$html .= '</td>';
// Birth anniversary
if (isset($birth_dates[0]) && $birth_dates[0]->gregorianYear() >= 1550 && $birth_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
$birt_by_decade[(int) ($birth_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
$anniversary = Date::getAge($birth_dates[0], null, 2);
} else {
$anniversary = '';
}
$html .= '<td class="center" data-sort="' . -$individual->getEstimatedBirthDate()->julianDay() . '">' . $anniversary . '</td>';
// Birth place
$html .= '<td>';
foreach ($individual->getAllBirthPlaces() as $n => $birth_place) {
$tmp = new Place($birth_place, $individual->getTree());
if ($n > 0) {
$html .= '<br>';
}
$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
}
$html .= '</td>';
// Number of children
$number_of_children = $individual->getNumberOfChildren();
$html .= '<td class="center" data-sort="' . $number_of_children . '">' . I18N::number($number_of_children) . '</td>';
// Death date
$death_dates = $individual->getAllDeathDates();
$html .= '<td data-sort="' . $individual->getEstimatedDeathDate()->julianDay() . '">';
foreach ($death_dates as $num => $death_date) {
if ($num) {
$html .= '<br>';
}
$html .= $death_date->display(true);
}
$html .= '</td>';
// Death anniversary
if (isset($death_dates[0]) && $death_dates[0]->gregorianYear() >= 1550 && $death_dates[0]->gregorianYear() < 2030 && !isset($unique_indis[$individual->getXref()])) {
$birt_by_decade[(int) ($death_dates[0]->gregorianYear() / 10) * 10] .= $individual->getSex();
$anniversary = Date::getAge($death_dates[0], null, 2);
} else {
$anniversary = '';
}
$html .= '<td class="center" data-sort="' . -$individual->getEstimatedDeathDate()->julianDay() . '">' . $anniversary . '</td>';
// Age at death
if (isset($birth_dates[0]) && isset($death_dates[0])) {
$age_at_death = Date::getAge($birth_dates[0], $death_dates[0], 0);
$age_at_death_sort = Date::getAge($birth_dates[0], $death_dates[0], 2);
if (!isset($unique_indis[$individual->getXref()]) && $age >= 0 && $age <= $max_age) {
$deat_by_age[$age_at_death] .= $individual->getSex();
}
} else {
$age_at_death = '';
$age_at_death_sort = PHP_INT_MAX;
}
$html .= '<td class="center" data-sort="' . $age_at_death_sort . '">' . $age_at_death . '</td>';
// Death place
$html .= '<td>';
foreach ($individual->getAllDeathPlaces() as $n => $death_place) {
$tmp = new Place($death_place, $individual->getTree());
if ($n > 0) {
$html .= '<br>';
}
$html .= '<a href="' . $tmp->getURL() . '" title="' . strip_tags($tmp->getFullName()) . '">';
$html .= FunctionsPrint::highlightSearchHits($tmp->getShortName()) . '</a>';
}
$html .= '</td>';
// Last change
$html .= '<td data-sort="' . $individual->lastChangeTimestamp(true) . '">' . $individual->lastChangeTimestamp() . '</td>';
// Filter by sex
$html .= '<td hidden>' . $individual->getSex() . '</td>';
// Filter by birth date
$html .= '<td hidden>';
if (!$individual->canShow() || Date::compare($individual->getEstimatedBirthDate(), $hundred_years_ago) > 0) {
$html .= 'Y100';
} else {
$html .= 'YES';
}
$html .= '</td>';
// Filter by death date
$html .= '<td hidden>';
// Died in last 100 years? Died? Not dead?
if (isset($death_dates[0]) && Date::compare($death_dates[0], $hundred_years_ago) > 0) {
$html .= 'Y100';
} elseif ($individual->isDead()) {
$html .= 'YES';
} else {
$html .= 'N';
}
$html .= '</td>';
// Filter by roots/leaves
$html .= '<td hidden>';
if (!$individual->getChildFamilies()) {
$html .= 'R';
} elseif (!$individual->isDead() && $individual->getNumberOfChildren() < 1) {
$html .= 'L';
$html .= ' ';
}
$html .= '</td>';
$html .= '</tr>';
$unique_indis[$individual->getXref()] = true;
}
$html .= '
</tbody>
</table>
<div id="indi_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($deat_by_decade, I18N::translate('Decade of death')) . '
</td>
</tr>
<tr>
<td colspan="2">
' . self::chartByAge($deat_by_age, I18N::translate('Age related to death year')) . '
</td>
</tr>
</table>
</div>
</div>';
return $html;
}