
Reactions
Universal reactions for MODX 3 — likes, GitHub-style sets, tops and trending on any object


Sort and list resources, products, and other objects by reactions with a leftJoin on the ReactionAggregate model.
The reactions_aggregates table stores precomputed metrics:
| Field | Description |
|---|---|
object_class | Object class (modResource, msProduct, …) |
object_id | Object ID |
context | MODX context |
counts | JSON with per-type counters |
likes | Sum of like + up |
dislikes | Sum of dislike + down |
rating | likes − dislikes |
total | Sum of all reactions |
trending_score | Trending score |
[[!pdoResources?
&parents=`0`
&depth=`10`
&limit=`12`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = 'modResource' AND Aggregate.context = 'web'"}}`
&sortby=`Aggregate.likes`
&sortdir=`DESC`
&tpl=`tpl.article.card`
]]{'!pdoResources' | snippet : [
'parents' => 0,
'depth' => 10,
'limit' => 12,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = \'modResource\' AND Aggregate.context = \'web\'"}}',
'sortby' => 'Aggregate.likes',
'sortdir' => 'DESC',
'tpl' => 'tpl.article.card',
]}[[!pdoResources?
&parents=`5`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = 'modResource'"}}`
&sortby=`Aggregate.rating`
&sortdir=`DESC`
&limit=`10`
]]{'!pdoResources' | snippet : [
'parents' => 5,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = \'modResource\'"}}',
'sortby' => 'Aggregate.rating',
'sortdir' => 'DESC',
'limit' => 10,
]}[[!pdoResources?
&parents=`0`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = 'modResource'"}}`
&sortby=`Aggregate.trending_score`
&sortdir=`DESC`
&limit=`8`
]]{'!pdoResources' | snippet : [
'parents' => 0,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = \'modResource\'"}}',
'sortby' => 'Aggregate.trending_score',
'sortdir' => 'DESC',
'limit' => 8,
]}In the tpl.article.card chunk:
<h3>[[+pagetitle]]</h3>
<p>👍 [[!ReactionsCount? &object=`[[+id]]` &format=`{LIKES}`]]</p><h3>{$pagetitle}</h3>
<p>👍 {'!ReactionsCount' | snippet : ['object' => $id, 'format' => '{LIKES}']}</p>Or via a placeholder:
[[!ReactionsCount? &object=`[[+id]]` &format=`{LIKES}` &toPlaceholder=`likes_[[+id]]`]]
<span>[[+likes_[[+id]]]]</span>{'!ReactionsCount' | snippet : [
'object' => $id,
'format' => '{LIKES}',
'toPlaceholder' => ('likes_' ~ $id),
]}
<span>{$_modx->getPlaceholder('likes_' ~ $id)}</span>[[!pdoPage?
&element=`pdoResources`
&parents=`0`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = 'modResource'"}}`
&sortby=`Aggregate.likes`
&sortdir=`DESC`
&tpl=`tpl.article.card`
]]
[[!+page.nav]]{'!pdoPage' | snippet : [
'element' => 'pdoResources',
'parents' => 0,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = \'modResource\'"}}',
'sortby' => 'Aggregate.likes',
'sortdir' => 'DESC',
'tpl' => 'tpl.article.card',
]}
{$_modx->getPlaceholder('page.nav')}In a list row template (chunk tpl / $results loop):
<article>
<h2><a href="[[~[[+id]]]]">[[+pagetitle]]</a></h2>
[[!ReactionsCount?
&object=`[[+id]]`
&format=`👍 {LIKES}`
]]
</article>{foreach $results as $row}
<article>
<h2><a href="{$row.id | url}">{$row.pagetitle}</a></h2>
{'!ReactionsCount' | snippet : [
'object' => $row.id,
'format' => '👍 {LIKES}',
]}
</article>
{/foreach}[[!pdoResources?
&parents=`0`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = 'modResource'"}}`
&where=`{"Aggregate.likes:>":0}`
&sortby=`Aggregate.likes`
&sortdir=`DESC`
]]{'!pdoResources' | snippet : [
'parents' => 0,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = modResource.id AND Aggregate.object_class = \'modResource\'"}}',
'where' => '{"Aggregate.likes:>":0}',
'sortby' => 'Aggregate.likes',
'sortdir' => 'DESC',
]}Reactions package is registered in xPDO: the ReactionAggregate class is available under the full name Reactions\Model\ReactionAggregate.leftJoin returns NULL. Those rows end up last with DESC or first with ASC.php core/components/reactions/cli.php recount.