Skip to main content

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:

  1. Implement cart.onGetCart() in JS so it returns the CartState from your module (Razor renders it inline, or an AJAX endpoint exposes it).
  2. Emit neuroon:cart-update whenever 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 through onGetCart().

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, total are 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 via EventLog or poll client-side in cart.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 jQuery dispatchEvent, and fire neuroon:cart-update from 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

  1. Open the browser console on a product page.
  2. Subscribe: window.addEventListener('neuroon:cart-update', e => console.log(e.detail));.
  3. 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