Skip to main content

ViewObject Pattern

The ViewObject pattern in upp-data wraps DataObject instances with computed properties, event handling, and a unified Proxy interface. ViewObjects enhance the raw data model for UI consumption while keeping the DataObject as the single source of truth.

ViewObject Base Class (from base.ts)

The ViewObject<T extends DataObject> abstract class wraps a DataObject with computed properties and event handling.

Proxy Pattern

The get Proxy() method returns a JavaScript Proxy that:

  • For get: Returns ViewObject properties first, then falls through to the underlying DataObject. Functions from DataObject are bound to the DataObject context. The View property is explicitly excluded to avoid recursive access.
  • For set: Sets on ViewObject first if the property exists there, then on DataObject, then on ViewObject as fallback.
  • For has: Checks both ViewObject and DataObject.
  • For delete: Delegates to DataObject first, then ViewObject.

The exported type is defined as:

export type ViewType<T, U> = T & {
[K in keyof U as K extends keyof T ? never : K]: U[K];
};

This merges ViewObject and DataObject types while prioritizing ViewObject's properties (ViewObject keys are excluded from the DataObject contribution to avoid conflicts).

The factory function pattern:

export type PlaceView = ViewType<_PlaceView, Place>;

export function PlaceViewProxy(object: Place, data: dataService): PlaceView {
return new _PlaceView(object, data).Proxy as PlaceView;
}

Lifecycle

  1. DataObject's get View() calls viewProxy (abstract, each class overrides) which creates the ViewObject.
  2. The ViewObject is stored as a WeakRef on the DataObject (_objectviewref).
  3. When accessed via aliveItems.get(dataObject), if no live reference exists, a new one is created and doRegister() is called.
  4. doRegister() subscribes to OnRefresh of the underlying DataObject and its children.
  5. Subscriptions use WeakRef(this) to avoid preventing GC.
  6. OnDestroy() unsubscribes all subscriptions.

Refresh Flow

  • OnDataRefresh (Subject): Fires immediately on data changes.
  • OnViewRefresh (Subject): Fires asynchronously (via Promise.resolve().then()) for UI updates, with deduplication via the _scheduled flag.
  • DoRefreshView(now?): Triggers both. If now=false, waits for a clock tick via clockService.OnRefreshTick.

aliveItems Class

Manages a WeakRef-based cache of ViewObjects:

  • Uses WeakMap<DataObject, WeakRef<ViewObject<DataObject>>>.
  • FinalizationRegistry tracks when ViewObjects are garbage collected and cleans up the map.
  • get(object): Returns the existing ViewObject or creates a new one and calls doRegister().
  • has(object): Checks if an alive reference still exists.
  • Debug: itemssize, logstatus().

ViewObject Classes

PlaceView (wraps Place)

Computed properties:

  • user — UserView
  • tables — QrCodeView[]
  • products — ProductView[]
  • offers — OfferView[]
  • extras — ExtraView[]
  • discounts — DiscountView[]
  • employees — EmployeeView[]
  • tickets — TicketView[]

Features:

  • catalog — Catalog instance for loading product catalog.
  • search — SearchTool for product search.
  • GroupedProducts — Products grouped by parent (families, groups).
  • AlertSize / AlertTables — Tables requiring waiter attention.

Configuration:

  • taxrate, TicketPrepayment, TicketPreparation
  • CanCash, CanCard, CanAccount
  • OpenOnCash, OpenOnCard
  • SendVFTEnabled, SendBAIEnabled

Ticket generation:

  • NextDeviceTicket(series, invoice) — Series/invoice numbering.
  • InitializeTickets — Initializes ticket store from server or local cache.

Actions:

  • CreateTables(size) — Creates new QR code tables.
  • DeleteTables(size) — Deletes tables.
  • PrintQrCodes() — Generates and downloads QR stickers PDF.
  • LoadCatalog() — Loads catalog if not already loaded.
  • doSearch(criteria) — Searches products and preselects.

TicketView (wraps Ticket)

Computed properties:

  • place — PlaceView
  • qrcode — QrCodeView
  • products — TicketProductView[]
  • offers — TicketOfferView[]
  • extras — TicketExtraView[]
  • discounts — TicketDiscountView[]
  • mixed — MixedPayment[]
  • changes — TicketChange[]
  • lastchange — Last TicketChange
  • invoice — TicketInvoice
  • priceinfo — PriceType (calculated totals)

Status flags:

  • IsRecent, IsEmpty, IsOpened, IsClosed
  • IsReady, IsToPay, IsPaid, IsCancel

Payment:

  • payment getter/setter with auto-print and cash drawer on payment.
  • Status transitions (AC, RD, PD, PP, CC).

Actions:

  • ToCart(product, options, amount, info) — Adds product to ticket.
  • AddOneOf(ticketproduct) — Increments quantity.
  • DelOneOf(ticketproduct) — Decrements quantity.
  • AddSplit() — Splits payment.
  • AddDiscount(discount) — Applies discount.
  • OnCommit(payment, transaction) — Commits ticket (TicketCommit flow).
  • OnOpen() — Reopens ticket for editing.
  • OnCancel() — Cancels ticket.
  • OnReady() — Marks ticket ready.
  • OnMerge(merge) — Merges another ticket into this one.

Internal classes:

  • TicketCommit — Handles CommitTicket flow (order number, TicketChange, Verifactu/Ticketbai, print, commit).
  • TicketPrint — Manages print/drawer requests.

Price calculation (_PriceInfo):

  1. Clears offers and groups products.
  2. Applies offers.
  3. Groups products again.
  4. Applies extras.
  5. Applies discounts.
  6. Sums products, extras, subtracts discounts into PriceInfo.

UserView (wraps User)

  • employees — EmployeeView[] (valid employees).
  • places — PlaceView[] (owned places + staff places).
  • IsActive — Whether user has an active session.
  • IsPlaceOwner — Whether user owns the current place.
  • IsAllowedTo(permission) — Permission check (owner or employee).

ProductView (wraps Product)

  • selects — PreselectView[]
  • categories — CategoryView[]
  • options — ProductOptView[]
  • parent — ProductView (for grouped products).
  • groupitems — ProductView[] (children of a group).
  • ToCart — Whether product can be added to cart.
  • calcinfo — Price calculation info.
  • Subscribes to OnChildChanged to re-register when options/categories change.

CategoryView (wraps Category)

  • product — ProductView
  • options — ProductOptView[]
  • enabledoptions — Options that are enabled.
  • validdepends — CategoryDep[]
  • checkoptions — ProductOptView[] for validation.
  • IsSuitable — Whether category selection is valid.

QrCodeView (wraps QrCode)

  • place — PlaceView
  • tickets — TicketView[] (valid tickets).
  • IsValid — Whether table is not deleted.
  • Attention — Whether table needs waiter attention.
  • TableStatus — wait | cnfg | goto | busy | work | free.
  • ThumbInfo — icon and color for UI.

EmployeeView (wraps Employee)

  • user — UserView
  • place — PlaceView
  • IsActive — Whether employee's user has active session.
  • IsAllowedTo(permission) — Permission check.

OfferView (wraps Offer)

  • place — PlaceView
  • periods — OfferPeriod[]
  • products — OfferProduct[] with ProductView.
  • GroupProducts — Products grouped for display.
  • AppliesTo(ticket) — Whether offer applies to ticket.

ExtraView (wraps Extra)

  • place — PlaceView
  • periods — ExtraPeriod[]
  • products — ProductView[] (via ExtraProduct).
  • tables — QrCodeView[] (via ExtraTable).
  • AppliesTo(ticketproduct) — Whether extra applies.

DiscountView (wraps Discount)

  • place — PlaceView
  • periods — DiscountPeriod[]
  • products — ProductView[] (via DiscountProduct).
  • AppliesTo(ticket) — Whether discount applies to ticket.

FamilyView (wraps Family)

  • periods — FamilyPeriod[]
  • products — FamilyProduct[]
  • ProductsLength — Count of products in family.

PreselectView (wraps Preselect)

  • options — ProductOptView[]
  • price — Calculated preselect price.
  • IsInvalid — Whether selection is invalid.
  • IsCompleted — Whether all required options are selected.

ProductOptView (wraps ProductOpt)

  • product — ProductView
  • category — CategoryView
  • calcinfo — Price calculation for option.

TicketProductView, TicketOfferView, TicketExtraView, TicketDiscountView

  • Wrap ticket line items with computed properties (charge, amount, calcinfo).
  • Provide product, offer, extra, discount references.
  • Support grouping and display logic for ticket UI.

Lifecycle Diagram

flowchart TB
subgraph DataObject
DO[DataObject]
DO_View[get View]
DO_viewProxy[viewProxy getter]
DO_weakref[_objectviewref: WeakRef]
end

subgraph ViewObject
VO[ViewObject]
VO_Proxy[Proxy]
VO_doRegister[doRegister]
VO_OnDestroy[OnDestroy]
end

subgraph aliveItems
AI[aliveItems]
AI_get[get object]
AI_WeakMap[WeakMap]
AI_Finalization[FinalizationRegistry]
end

subgraph Subscriptions
OnRefresh[OnRefresh]
WeakRefSub[WeakRef in subscription]
end

DO_View --> DO_viewProxy
DO_viewProxy -->|creates| VO
DO_View -->|stores| DO_weakref
DO_weakref -.->|deref| VO

AI_get -->|object| AI
AI -->|get or create| VO
AI -->|stores| AI_WeakMap
AI_WeakMap -.->|WeakRef| VO
AI -->|on create| VO_doRegister

VO_doRegister -->|subscribes| OnRefresh
OnRefresh -->|uses| WeakRefSub
WeakRefSub -.->|avoids strong ref| VO

VO -->|returns| VO_Proxy
VO_Proxy -->|get/set/has/delete| DO
VO_Proxy -->|get/set| VO

VO_OnDestroy -->|unsubscribes| OnRefresh
AI_Finalization -->|on GC| AI_WeakMap

DataObject.View → ViewObject → Proxy Flow

sequenceDiagram
participant UI as UI Component
participant AI as aliveItems
participant DO as DataObject
participant VO as ViewObject
participant Proxy as Proxy

UI->>AI: get(place)
AI->>DO: place.View (or create)
DO->>DO: viewProxy
DO->>VO: new _PlaceView(place, data)
VO->>VO: doRegister()
VO->>DO: subscribe OnRefresh
AI->>VO: return Proxy
AI->>VO: doRegister()
UI->>Proxy: place.tables
Proxy->>VO: get tables
VO-->>UI: QrCodeView[]
UI->>Proxy: place.name
Proxy->>DO: get name
DO-->>UI: string