Vactory flag
Enable flag for various content types.
Vactory Flag simplifies the process for developers to easily enable flag features for various content types.
In this tutorial, we'll use Academy (vactory_academy) content as an example.
Below are the steps to follow
1. Enable the module with the following drush command
drush en vactory_decoupled_flag -y
2. Enabling flags for content type
Go to content type configuration
/admin/structure/types/manage/vactory_academy
and enable flags for this content type, as illustrated in the image below
3. Expose flag fields to the frontend
To enable flag features for any content type listing, add the following fields to the relevant widget configuration (widget of type json_api_collection)
- has_flag: Indicates if the flag is enabled for this content type.
- is_flagged: Indicates if a specific node is flagged by the current user.
collection: type: json_api_collection label: 'JSON:API' options: '#required': TRUE '#default_value': id: "vactory_academy_favorite" resource: node--vactory_academy filters: - fields[node--vactory_academy]=is_flagged,has_flag,...
4. Adding the Flag in the Frontend
Receive the has_flag and is_flagged fields, then include them in the normalizer.
export const normalizeNode = (node) => { return { id: node?.drupal_internal__nid, hasFlag: node?.has_flag, isFlagged: node?.is_flagged, ... }}
In the relevant widget (card componenet), add the Flag component as follows
modules/academy/AcademyCard.jsx
import { Flag } from "@/ui"
export const AcademyCard = ({ title, id, isFlagged, hasFlag,}) => { ... {hasFlag && ( <Flag id={id} title={title} module="default_flag" className="absolute right-4 top-4 cursor-pointer" isFlagged={isFlagged} reloadPage={reloadPage} /> )} ...}
We can also include it on the detail page by adding the two fields to the node configuration
modules/academy/AcademyNode.jsx
export const config = { id: "node--vactory_academy", params: { fields: { "node--vactory_academy": "has_flag,is_flagged,...", }, ... },}
5. Create a DF to list content flagged by the current user
Create a JSON:API collection for the desired content type and ensure you have added the following elements:
- Expose our key fields, is_flagged and has_flag
- Add this token to ensure the retrieval of only the flagged content by the current user [vactory:flagged_nodes:vactory_academy]
- Include this cache configuration in your json_api_collection to make your listing cacheable per user and flagging entity
cache_tags: - flagging_listcache_contexts - user
Here is the complete example
collection: type: json_api_collection label: 'JSON:API' options: '#required': TRUE '#default_value': id: "vactory_academy_favorite" resource: node--vactory_academy filters: - fields[node--vactory_academy]=is_flagged,has_flag,... ... - "[vactory:flagged_nodes:vactory_academy]" cache_tags: - flaggging_list cache_contexts: - user
After setting up the widget for this dynamic field on the frontend, define the 'reloadPage' function and pass it to the card componenet. This helps update the data when a user unflags content.
modules/academy/AcademyFavoriteWidget.jsx
const FavoriteAcademy = ({ data }) => { const reloadPage = async () => { const nids = await getCurrentUserFalggedArticles() setFilters((prev) => { let filters = { ...prev, } filters.page = { ...filters.page, offset: 0, } filters.filter.internal_favourite = { condition: { path: "drupal_internal__nid", operator: "IN", value: nids?.nids, }, } return filters }) } const getCurrentUserFalggedArticles = async () => { const response = await drupal.fetch(`api/flagging/all/vactory_academy`, { withAuth: true, method: "GET", }) if (response.ok) { return await response.json() } return [] } useUpdateEffect(async () => { const nids = await getCurrentUserFalggedArticles() setFilters((prev) => { let filters = { ...prev, } // Update pager. filters.page = { ...filters.page, offset: (pager - 1) * (filters?.page?.limit || defaulPagetLimit), } filters.filter.internal_favourite = { condition: { path: "drupal_internal__nid", operator: "IN", value: nids?.nids, }, } return filters }) }, [pager]) return ( ... <AcademyCard {...post} reloadPage={reloadPage} /> ... )}