123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- Smalltalk current createPackage: 'Trapped-Frontend'!
- Object subclass: #TrappedDataCarrier
- instanceVariableNames: 'target model chain source'
- package: 'Trapped-Frontend'!
- !TrappedDataCarrier methodsFor: 'accessing'!
- chain: aProcessingChain
- chain := aProcessingChain
- !
- source
- ^source
- !
- source: anObject
- source := anObject
- !
- target
- ^target
- !
- target: anObject
- target := anObject
- !
- value
- ^model
- !
- value: anObject
- model := anObject
- !
- value: anObject whenDifferentFrom: anotherObject
- anObject = anotherObject ifFalse: [ self value: anObject ]
- ! !
- !TrappedDataCarrier methodsFor: 'converting'!
- falseAsNilValue
- | value |
- value := self value.
- value = false ifTrue: [ ^nil ] ifFalse: [ ^value ]
- ! !
- !TrappedDataCarrier methodsFor: 'initialization'!
- initialize
- super initialize.
- model := true
- ! !
- !TrappedDataCarrier class methodsFor: 'not yet classified'!
- on: aProcessingChain target: anObject
- ^self new
- chain: aProcessingChain;
- target: anObject;
- yourself
- ! !
- TrappedDataCarrier subclass: #TrappedDataCarrierToModel
- instanceVariableNames: 'index'
- package: 'Trapped-Frontend'!
- !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
- proceed
- index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
- (chain processorNo: index) toModel: self
- ! !
- TrappedDataCarrier subclass: #TrappedDataCarrierToView
- instanceVariableNames: 'index'
- package: 'Trapped-Frontend'!
- !TrappedDataCarrierToView methodsFor: 'not yet classified'!
- proceed
- index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
- (chain processorNo: index) toView: self
- ! !
- Object subclass: #TrappedProcessingChain
- instanceVariableNames: 'processors'
- package: 'Trapped-Frontend'!
- !TrappedProcessingChain methodsFor: 'accessing'!
- firstProcessorNo
- ^1
- !
- lastProcessorNo
- ^processors size
- !
- processorNo: aNumber
- ^processors at: aNumber
- !
- processors: anArray
- processors := anArray
- ! !
- !TrappedProcessingChain methodsFor: 'action'!
- forSnapshot: aSnapshot andBrush: aTagBrush
- | toViewCarrier toModelCarrier |
- toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
- toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
- processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].
- toViewCarrier source: aSnapshot.
- toModelCarrier source: aTagBrush.
- toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]
- ! !
- !TrappedProcessingChain class methodsFor: 'instance creation'!
- new: anArray
- (anArray anySatisfy: [ :each | each isExpectingModelData ])
- ifFalse: [ anArray add: self dataTerminator ]
- ifTrue: [ anArray addFirst: self blackboardReaderWriter ].
- ^self new
- processors: anArray;
- yourself
- !
- newFromProcessorSpecs: anArray
- ^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each asTrapProcSendTo: TrappedProcessor ])
- ! !
- !TrappedProcessingChain class methodsFor: 'private'!
- blackboardReaderWriter
- ^TrappedProcessorBlackboard new
- !
- dataTerminator
- ^TrappedProcessorTerminator new
- ! !
- Object subclass: #TrappedProcessor
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedProcessor commentStamp!
- I am a processing step in TrappedProcessingChain.
- I am stateless flyweight (aka servant)
- and will get all necessary data as arguments in API calls.
- My public API is:
- - installToView:toModel:
- This gets two TrappedDataCarriers set up without actual data
- and at the beginning of their chains. It should do one-time
- installation task needed (install event handlers etc.).
- To start a chain, do: dataCarrier copy value: data; proceed.
- - toView:
- This performs transformation of TrappedDataCarrier on its way from model to view.
- Should call aDataCarrier proceed to proceed to subsequent step.
- - toModel:
- This performs transformation of TrappedDataCarrier on its way from view to model.
- Should call aDataCarrier proceed to proceed to subsequent step.!
- !TrappedProcessor methodsFor: 'data transformation'!
- toModel: aDataCarrier
- "by default, proceed"
- aDataCarrier proceed
- !
- toView: aDataCarrier
- "by default, proceed"
- aDataCarrier proceed
- ! !
- !TrappedProcessor methodsFor: 'installation'!
- installToView: aDataCarrier toModel: anotherDataCarrier
- "by default, do nothing"
- ! !
- !TrappedProcessor methodsFor: 'testing'!
- isExpectingModelData
- ^false
- ! !
- !TrappedProcessor class methodsFor: 'factory'!
- contents
- ^TrappedProcessorContents new
- ! !
- TrappedProcessor subclass: #TrappedDataExpectingProcessor
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedDataExpectingProcessor commentStamp!
- I answer true to isExpectingModelData and serve as a base class
- for processor that present / change model data.
- When at least one of my instances is present in the chain,
- automatic databinding processor is added at the beginning
- (the data-binding scenario); otherwise, the chain
- is run immediately with true as data (run-once scenario).!
- !TrappedDataExpectingProcessor methodsFor: 'testing'!
- isExpectingModelData
- ^true
- ! !
- TrappedDataExpectingProcessor subclass: #TrappedProcessorContents
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedProcessorContents commentStamp!
- I put data into target via contents: in toView:!
- !TrappedProcessorContents methodsFor: 'data transformation'!
- toView: aDataCarrier
- aDataCarrier toTargetContents
- ! !
- TrappedProcessor subclass: #TrappedProcessorBlackboard
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedProcessorBlackboard commentStamp!
- I am used internally to fetch data from blackboard
- or write it back.
- I am added to the beginning of the chain
- when the chain contains at least one element
- that isExpectingModelData (see TrappedDataExpectingProcessor).!
- !TrappedProcessorBlackboard methodsFor: 'data transformation'!
- toModel: aDataCarrier
- aDataCarrier modifyTarget
- ! !
- !TrappedProcessorBlackboard methodsFor: 'installation'!
- installToView: aDataCarrier toModel: anotherDataCarrier
- | snap |
- snap := anotherDataCarrier target.
- snap watch: [ :data |
- (aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
- snap do: [ aDataCarrier copy value: data; proceed ] ].
- aDataCarrier value: false
- ! !
- TrappedProcessor subclass: #TrappedProcessorTerminator
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedProcessorTerminator commentStamp!
- I do not proceed in toView:.
- I am added automatically to end of chain when it does not contain
- any element that isExpectingModelData (see TrappedDataExpectingProcessor).!
- !TrappedProcessorTerminator methodsFor: 'data transformation'!
- toView: aDataCarrier
- "stop"
- ! !
- Object subclass: #TrappedSingleton
- instanceVariableNames: ''
- package: 'Trapped-Frontend'!
- !TrappedSingleton methodsFor: 'action'!
- start: args
- ^ self subclassResponsibility
- ! !
- TrappedSingleton class instanceVariableNames: 'current'!
- !TrappedSingleton class methodsFor: 'accessing'!
- current
- ^ current ifNil: [ current := self new ]
- ! !
- !TrappedSingleton class methodsFor: 'action'!
- start: args
- self current start: args
- ! !
- TrappedSingleton subclass: #Trapped
- instanceVariableNames: 'registry'
- package: 'Trapped-Frontend'!
- !Trapped methodsFor: 'accessing'!
- byName: aString
- ^ registry at: aString
- !
- register: aListKeyedEntity
- self register: aListKeyedEntity name: aListKeyedEntity class name
- !
- register: aListKeyedEntity name: aString
- registry at: aString put: aListKeyedEntity
- ! !
- !Trapped methodsFor: 'action'!
- start: args
- args do: [ :each | self register: each ].
- self injectToElement: document
- ! !
- !Trapped methodsFor: 'initialization'!
- initialize
- super initialize.
- registry := #{}.
- ! !
- !Trapped methodsFor: 'private'!
- cloneAndInject: anObject
- ^anObject asJQuery clone
- each: [ :i :each | self injectToElement: each ];
- get: 0
- !
- descend: anArray snapshotDo: aBlock
- | tpsc |
- tpsc := TrappedPathStack current.
- tpsc append: anArray do: [
- | path model |
- path := tpsc elements copy.
- model := self byName: path first.
- aBlock value: (TrappedSnapshot new path: path model: model)
- ]
- !
- injectToChildren: anElement
- | child |
- child := anElement firstChild.
- [ child isNil ] whileFalse: [ self injectToElement: child. child := child nextSibling ]
- !
- injectToElement: anElement
- | jq |
- jq := anElement asJQuery.
- (jq attr: 'data-trap') ifNotNil: [ :attr |
- jq removeAttr: 'data-trap'.
- (Trapped parse: attr) do: [ :rule |
- (HTMLCanvas onJQuery: jq) root trap: rule first processors: (rule at: 2 ifAbsent: [#()]) ] ].
- self injectToChildren: anElement
- ! !
- !Trapped class methodsFor: 'accessing'!
- parse: aString
- ^ (aString tokenize: '.') collect: [ :rule |
- (rule tokenize: ':') collect: [ :message |
- | result stack anArray |
- anArray := message tokenize: ' '.
- result := #().
- stack := { result }.
- anArray do: [ :each |
- | asNum inner close |
- close := 0.
- inner := each.
- [ inner notEmpty and: [ inner first = '(' ]] whileTrue: [ inner := inner allButFirst. stack add: (stack last add: #()) ].
- [ inner notEmpty and: [ inner last = ')' ]] whileTrue: [ inner := inner allButLast. close := close + 1 ].
- (inner notEmpty and: [ inner first = '#' ]) ifTrue: [ inner := { inner allButFirst } ].
- asNum := inner isString ifTrue: [ (inner ifEmpty: [ 'NaN' ]) asNumber ] ifFalse: [ inner ].
- asNum = asNum ifTrue: [ stack last add: asNum ] ifFalse: [
- inner ifNotEmpty: [ stack last add: inner ] ].
- close timesRepeat: [ stack removeLast ] ].
- result ] ]
- ! !
- !Trapped class methodsFor: 'private'!
- loop: model before: endjq do: aBlock
- model withIndexDo: [ :item :i |
- | env envjq |
- envjq := document createDocumentFragment asJQuery.
- {i} trapDescend: [ (HTMLCanvas onJQuery: envjq) root with: aBlock ].
- envjq insertBefore: endjq ]
- !
- loop: model between: start and: end do: aBlock
- (start asJQuery nextUntil: end element) remove.
- start with: [ :html | model ifNotNil: [
- self loop: model before: end asJQuery do: aBlock
- ]]
- ! !
- TrappedSingleton subclass: #TrappedPathStack
- instanceVariableNames: 'elements'
- package: 'Trapped-Frontend'!
- !TrappedPathStack methodsFor: 'accessing'!
- elements
- ^elements
- ! !
- !TrappedPathStack methodsFor: 'descending'!
- append: anArray do: aBlock
- self with: elements, anArray do: aBlock
- !
- with: anArray do: aBlock
- | old |
- old := elements.
- [ elements := anArray.
- aBlock value ] ensure: [ elements := old ]
- ! !
- !TrappedPathStack methodsFor: 'initialization'!
- initialize
- super initialize.
- elements := #().
- ! !
- Object subclass: #TrappedSnapshot
- instanceVariableNames: 'path model'
- package: 'Trapped-Frontend'!
- !TrappedSnapshot methodsFor: 'accessing'!
- model
- ^model
- !
- path
- ^path
- !
- path: anArray model: aTrappedMW
- path := anArray.
- model := aTrappedMW
- ! !
- !TrappedSnapshot methodsFor: 'action'!
- do: aBlock
- TrappedPathStack current with: path do: [ aBlock value: model ]
- !
- modify: aBlock
- self model modify: self path allButFirst do: aBlock
- !
- read: aBlock
- self model read: self path allButFirst do: aBlock
- !
- watch: aBlock
- self model watch: self path allButFirst do: aBlock
- ! !
- !Object methodsFor: '*Trapped-Frontend'!
- asTrapProcSendTo: anObject
- self error: 'Trapped cannot use processor descriptor of ', self class name, ' type.'
- ! !
- !String methodsFor: '*Trapped-Frontend'!
- asTrapProcSendTo: anObject
- ^anObject perform: self
- ! !
- !Array methodsFor: '*Trapped-Frontend'!
- asTrapProcSendTo: anObject
- | selector args |
- selector := ''.
- args := #().
- self withIndexDo: [ :element :index | index odd
- ifTrue: [ selector := selector, element ]
- ifFalse: [ selector := selector, ':'. args add: element ] ].
- ^anObject perform: selector withArguments: args
- !
- trapDescend: aBlock
- Trapped current descend: self snapshotDo: aBlock
- ! !
- !HTMLCanvas methodsFor: '*Trapped-Frontend'!
- trapIter: path do: aBlock
- self with: [ :html | (html tag: 'script') at: 'type' put: 'application/x-beacon'; trapIter: path after: aBlock ]
- ! !
- !TagBrush methodsFor: '*Trapped-Frontend'!
- trap: path
- self trap: path processors: #()
- !
- trap: path processors: anArray
- path trapDescend: [ :snap |
- (TrappedProcessingChain newFromProcessorSpecs: anArray)
- forSnapshot: snap andBrush: self ]
- !
- trap: path read: aBlock
- path trapDescend: [ :snap |
- snap watch: [ :data |
- (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
- snap do: [ self with: [ :html | aBlock value: data value: html ] ]
- ]
- ]
- !
- trapGuard: anArray contents: aBlock
- #() trapDescend: [ :snap |
- | shown |
- shown := nil.
- self trap: anArray read: [ :gdata |
- | sanitized |
- sanitized := gdata ifNil: [ false ].
- shown = sanitized ifFalse: [
- shown := sanitized.
- shown
- ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
- ifFalse: [ self asJQuery hide; empty ] ] ] ]
- !
- trapIter: path after: aBlock
- | end |
- end := TagBrush fromJQuery: ('<script type="application/x-beacon" />' asJQuery insertAfter: self asJQuery) canvas: canvas.
- self trap: path read: [ :model |
- Trapped loop: model between: self and: end do: aBlock.
- ]
- ! !
|