DNN · Cart bridge
Unlike WooCommerce (which has standardized jQuery events), DNN e-commerce modules do not share a common API. The cart bridge in DNN is manual: your code emits window.dispatchEvent(new CustomEvent('neuroon:cart-update')) at the points where your module mutates the cart.
Bridge contract
The widget listens to neuroon:cart-update and, on receipt, calls config.cart.onGetCart() to re-read the cart from your host. It ignores event.detail — only dispatch with no payload (verified at widget/src/context/CartContext.tsx:165-191).
So your work on DNN is:
- Implement
cart.onGetCart()in JS so it returns theCartStatefrom your module (Razor renders it inline, or an AJAX endpoint exposes it). - Emit
neuroon:cart-updatewhenever your module mutates the cart (add, remove, update, clear).
No
event.detail: even if you dispatch with{ detail: cartState }, the current listener discards it. The state always flows throughonGetCart().
CartState schema
What cart.onGetCart() must return:
type CartItem = {
key: string; // unique line identifier (ID in your table or GUID)
id: string; // your externalId / productId (what you send in sync)
name: string;
price: string; // host-formatted: "$12.50"
quantity: number;
image?: string;
url?: string;
variant?: string; // "Size: 42, Color: Black"
externalId?: string; // host-platform id if it differs from the Neuroon id
maxQuantity?: number;
};
type CartState = {
items: CartItem[];
totalItems: number;
subtotal: string; // host-formatted
total: string; // host-formatted
currency: string; // ISO 4217: "EUR", "USD"
shipping?: CartShippingInfo; // see Widget → Cart integration for the full schema
};
Important:
price,subtotal,totalare strings already formatted by the host (with currency, locale separators). The widget does not recompute. This avoids rounding disputes between regions.
Reusable Razor partial
Create a partial that any cart view can render:
@model CartPayload
<script>
(function() {
var detail = @Html.Raw(JsonConvert.SerializeObject(Model));
window.dispatchEvent(new CustomEvent('neuroon:cart-update', { detail: detail }));
})();
</script>
And from your "Add to cart" view:
@{
var payload = new CartPayload {
Items = currentCart.Lines.Select(l => new CartItem {
ProductId = l.Sku,
Name = l.ProductName,
Price = l.UnitPrice,
Quantity = l.Quantity,
ImageUrl = l.ImageUrl
}).ToList(),
Total = currentCart.Total,
Currency = currentCart.Currency
};
}
@Html.Partial("_NeuroonCartUpdate", payload)
Hook per module (DNN Commerce, NB_Store, Hotcakes)
Each module exposes its own mechanism. These are the typical surfaces; verify against your exact module version (names may shift across majors).
NB_Store / NBrightBuy
NB_Store fires AJAX events on the frontend: nbsAddItem, nbsRemoveItem, nbsUpdateQty. Hook them from the Skin (Skin.ascx) or from a custom module loaded on every page:
<script>
(function() {
function debounce(fn, ms) {
var t; return function() { clearTimeout(t); t = setTimeout(fn, ms); };
}
var ping = debounce(function() {
window.dispatchEvent(new CustomEvent('neuroon:cart-update'));
}, 250);
document.addEventListener('nbsAddItem', ping);
document.addEventListener('nbsRemoveItem', ping);
document.addEventListener('nbsUpdateQty', ping);
})();
</script>
Server-side (when rendering the cart page or any view showing lines):
// In NBrightBuy.NBrightBuyUtils.AddCartItem or your wrapper, after persisting:
var snapshot = MapCartToNeuroonState(NBrightBuyUtils.GetCurrentCart(portalId));
ViewBag.NeuroonCart = snapshot; // canonical CartState
return PartialView("_NeuroonCartUpdate", snapshot);
DNN Commerce / Catalook
DNN Commerce uses the BasketController (MVC) with Add, Remove, Update actions. Pattern:
public class BasketController : Controller
{
public ActionResult Add(int productId, int qty)
{
// ... original logic ...
var payload = MapBasketToNeuroonState(this.CurrentBasket);
return PartialView("_NeuroonCartUpdate", payload);
}
}
_NeuroonCartUpdate.cshtml (push mode):
@model NeuroonCartState
<script>
window.dispatchEvent(new CustomEvent('neuroon:cart-update', {
detail: @Html.Raw(JsonConvert.SerializeObject(Model))
}));
</script>
Hotcakes Commerce
Hotcakes exposes CartController::AddToCart and fires the global event Hotcakes.Modules.Cart.OrderAddedToCart. Recommended ping pattern:
public override void OnOrderItemAdded(OrderItemEventArgs e)
{
base.OnOrderItemAdded(e);
// Render Razor partial that emits ping:
// <script>window.dispatchEvent(new CustomEvent('neuroon:cart-update'));</script>
}
If your Hotcakes version does not expose
OnOrderItemAdded, subscribe viaEventLogor poll client-side incart.ascx.
Custom or unlisted module
If your module fires a jQuery event (for example cart:updated), hook it globally from the Skin:
<script>
(function() {
var debounceTimer = null;
document.addEventListener('cart:updated', function(e) {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
// Ping mode (recommended): the widget calls your cart API
window.dispatchEvent(new CustomEvent('neuroon:cart-update'));
}, 250);
});
})();
</script>
The 250 ms debounce mirrors the WordPress plugin (see plugins/wordpress/cart-bridge).
If your module is not listed here: expose any server-side hook (
OnAdd/OnRemove/OnComplete) or jQuerydispatchEvent, and fireneuroon:cart-updatefrom the handler. The widget does not need to know which module you use.
SSR in Razor (.cshtml)
If your cart is rendered server-side and the cart is already populated in the model, emit the CustomEvent directly from the HTML:
<script>
window.dispatchEvent(new CustomEvent('neuroon:cart-update', {
detail: {
items: [
@foreach (var line in Model.Cart.Lines)
{
<text>{ productId: '@line.Sku', name: '@line.Name.Replace("'", "\\'")', price: @line.Price.ToString(System.Globalization.CultureInfo.InvariantCulture), quantity: @line.Quantity },</text>
}
],
total: @Model.Cart.Total.ToString(System.Globalization.CultureInfo.InvariantCulture),
currency: '@Model.Cart.Currency'
}
}));
</script>
Mind quote escaping in name. For safety, use JsonConvert.SerializeObject instead of inline string interpolation.
Validate that the bridge works
- Open the browser console on a product page.
- Subscribe:
window.addEventListener('neuroon:cart-update', e => console.log(e.detail));. - Add a product to the cart from your module. You should see the log with the full payload.
When NOT to emit the full payload
The widget also accepts the event without detail. In that case, the widget asks the server for the updated cart state. This is useful when the cart is complex (offers, discounts, tax) and you prefer not to expose those calculations in JavaScript:
window.dispatchEvent(new CustomEvent('neuroon:cart-update'));
Next steps
- Conversion tracking — server-side at order confirmation.
- Recipe · Custom cart bridge — generic schema and examples.