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
Viewproperty 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
- DataObject's
get View()callsviewProxy(abstract, each class overrides) which creates the ViewObject. - The ViewObject is stored as a WeakRef on the DataObject (
_objectviewref). - When accessed via
aliveItems.get(dataObject), if no live reference exists, a new one is created anddoRegister()is called. doRegister()subscribes toOnRefreshof the underlying DataObject and its children.- Subscriptions use
WeakRef(this)to avoid preventing GC. OnDestroy()unsubscribes all subscriptions.
Refresh Flow
OnDataRefresh(Subject): Fires immediately on data changes.OnViewRefresh(Subject): Fires asynchronously (viaPromise.resolve().then()) for UI updates, with deduplication via the_scheduledflag.DoRefreshView(now?): Triggers both. Ifnow=false, waits for a clock tick viaclockService.OnRefreshTick.
aliveItems Class
Manages a WeakRef-based cache of ViewObjects:
- Uses
WeakMap<DataObject, WeakRef<ViewObject<DataObject>>>. FinalizationRegistrytracks when ViewObjects are garbage collected and cleans up the map.get(object): Returns the existing ViewObject or creates a new one and callsdoRegister().has(object): Checks if an alive reference still exists.- Debug:
itemssize,logstatus().
ViewObject Classes
PlaceView (wraps Place)
Computed properties:
user— UserViewtables— 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,TicketPreparationCanCash,CanCard,CanAccountOpenOnCash,OpenOnCardSendVFTEnabled,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— PlaceViewqrcode— QrCodeViewproducts— TicketProductView[]offers— TicketOfferView[]extras— TicketExtraView[]discounts— TicketDiscountView[]mixed— MixedPayment[]changes— TicketChange[]lastchange— Last TicketChangeinvoice— TicketInvoicepriceinfo— PriceType (calculated totals)
Status flags:
IsRecent,IsEmpty,IsOpened,IsClosedIsReady,IsToPay,IsPaid,IsCancel
Payment:
paymentgetter/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):
- Clears offers and groups products.
- Applies offers.
- Groups products again.
- Applies extras.
- Applies discounts.
- 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
OnChildChangedto re-register when options/categories change.
CategoryView (wraps Category)
product— ProductViewoptions— ProductOptView[]enabledoptions— Options that are enabled.validdepends— CategoryDep[]checkoptions— ProductOptView[] for validation.IsSuitable— Whether category selection is valid.
QrCodeView (wraps QrCode)
place— PlaceViewtickets— 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— UserViewplace— PlaceViewIsActive— Whether employee's user has active session.IsAllowedTo(permission)— Permission check.
OfferView (wraps Offer)
place— PlaceViewperiods— OfferPeriod[]products— OfferProduct[] with ProductView.GroupProducts— Products grouped for display.AppliesTo(ticket)— Whether offer applies to ticket.
ExtraView (wraps Extra)
place— PlaceViewperiods— ExtraPeriod[]products— ProductView[] (via ExtraProduct).tables— QrCodeView[] (via ExtraTable).AppliesTo(ticketproduct)— Whether extra applies.
DiscountView (wraps Discount)
place— PlaceViewperiods— 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— ProductViewcategory— CategoryViewcalcinfo— Price calculation for option.
TicketProductView, TicketOfferView, TicketExtraView, TicketDiscountView
- Wrap ticket line items with computed properties (charge, amount, calcinfo).
- Provide
product,offer,extra,discountreferences. - 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