11import { nostojs } from "@nosto/nosto-js"
2- import { getText } from "@/utils/fetch"
2+ import { getText , postJSON } from "@/utils/fetch"
33import { customElement } from "../decorators"
44import { NostoElement } from "../Element"
55import { addRequest } from "../Campaign/orchestrator"
@@ -9,19 +9,32 @@ import { JSONResult } from "@nosto/nosto-js/client"
99 * NostoSectionCampaign is a custom element that fetches Nosto placement results and renders the results
1010 * using a Shopify section template via the Section Rendering API.
1111 *
12+ * default mode:
13+ * Section is fetched via /search endpoint with product handles as query param
14+ *
15+ * bundled mode:
16+ * Section is fetched via /cart/update.js endpoint with section name in payload
17+ * and campaign metadata is persisted in cart attributes
18+ *
1219 * @property {string } placement - The placement identifier for the campaign.
1320 * @property {string } section - The section to be used for Section Rendering API based rendering.
21+ * @property {string } handles - (internal) colon-separated list of product handles currently rendered
22+ * @property {string } mode - (internal) rendering mode, supported values: "section" (default), "bundled"
1423 */
1524@customElement ( "nosto-section-campaign" )
1625export class SectionCampaign extends NostoElement {
1726 /** @private */
1827 static attributes = {
1928 placement : String ,
20- section : String
29+ section : String ,
30+ handles : String ,
31+ mode : String
2132 }
2233
2334 placement ! : string
2435 section ! : string
36+ handles ! : string
37+ mode ?: "section" | "bundled"
2538
2639 async connectedCallback ( ) {
2740 this . toggleAttribute ( "loading" , true )
@@ -41,14 +54,39 @@ export class SectionCampaign extends NostoElement {
4154 if ( ! rec ) {
4255 return
4356 }
44- const markup = await getSectionMarkup ( this , rec )
45- this . innerHTML = markup
57+ const handles = rec . products . map ( product => product . handle ) . join ( ":" )
58+ const bundled = this . mode === "bundled"
59+ if ( ! bundled || this . handles !== handles ) {
60+ const markup = await ( bundled ? getBundledMarkup : getSectionMarkup ) ( this , handles , rec )
61+ if ( markup ) {
62+ this . innerHTML = markup
63+ }
64+ }
4665 api . attributeProductClicksInCampaign ( this , rec )
4766 }
4867}
4968
50- async function getSectionMarkup ( element : SectionCampaign , rec : JSONResult ) {
51- const handles = rec . products . map ( product => product . handle ) . join ( ":" )
69+ async function getBundledMarkup ( element : SectionCampaign , handles : string , rec : JSONResult ) {
70+ const target = new URL ( "/cart/update.js" , window . location . href )
71+ const payload = {
72+ attributes : {
73+ [ `nosto_${ element . placement } _title` ] : rec . title || "" ,
74+ [ `nosto_${ element . placement } _handles` ] : handles
75+ } ,
76+ sections : element . section ,
77+ }
78+ const reponse = await postJSON < { sections : Record < string , string > } > ( target . href , payload )
79+ const sectionHtml = reponse . sections [ element . section ] || ""
80+ if ( sectionHtml ) {
81+ const parser = new DOMParser ( )
82+ const doc = parser . parseFromString ( sectionHtml , "text/html" )
83+ return doc . querySelector ( `nosto-bundled-campaign[placement="${ element . placement } "]` ) ?. innerHTML || ""
84+ }
85+ return undefined
86+ }
87+
88+
89+ async function getSectionMarkup ( element : SectionCampaign , handles : string , rec : JSONResult ) {
5290 const target = new URL ( "/search" , window . location . href )
5391 target . searchParams . set ( "section_id" , element . section )
5492 target . searchParams . set ( "q" , handles )
0 commit comments