Trapped-Frontend.st 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. Smalltalk createPackage: 'Trapped-Frontend'!
  2. Object subclass: #TrappedDataCarrier
  3. instanceVariableNames: 'target model chain'
  4. package: 'Trapped-Frontend'!
  5. !TrappedDataCarrier methodsFor: 'accessing'!
  6. chain: aProcessingChain
  7. chain := aProcessingChain
  8. !
  9. target
  10. ^target
  11. !
  12. target: anObject
  13. target := anObject
  14. !
  15. value
  16. ^model
  17. !
  18. value: anObject
  19. model := anObject
  20. !
  21. value: anObject whenDifferentFrom: anotherObject
  22. anObject = anotherObject ifFalse: [ self value: anObject ]
  23. ! !
  24. !TrappedDataCarrier methodsFor: 'converting'!
  25. falseAsNilValue
  26. | value |
  27. value := self value.
  28. value = false ifTrue: [ ^nil ] ifFalse: [ ^value ]
  29. ! !
  30. !TrappedDataCarrier methodsFor: 'initialization'!
  31. initialize
  32. super initialize.
  33. model := true
  34. ! !
  35. !TrappedDataCarrier class methodsFor: 'not yet classified'!
  36. on: aProcessingChain target: anObject
  37. ^self new
  38. chain: aProcessingChain;
  39. target: anObject;
  40. yourself
  41. ! !
  42. TrappedDataCarrier subclass: #TrappedDataCarrierToModel
  43. instanceVariableNames: 'index'
  44. package: 'Trapped-Frontend'!
  45. !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
  46. proceed
  47. index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
  48. (chain processorNo: index) toModel: self
  49. ! !
  50. TrappedDataCarrier subclass: #TrappedDataCarrierToView
  51. instanceVariableNames: 'index'
  52. package: 'Trapped-Frontend'!
  53. !TrappedDataCarrierToView methodsFor: 'not yet classified'!
  54. proceed
  55. index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
  56. (chain processorNo: index) toView: self
  57. ! !
  58. Object subclass: #TrappedProcessingChain
  59. instanceVariableNames: 'processors'
  60. package: 'Trapped-Frontend'!
  61. !TrappedProcessingChain methodsFor: 'accessing'!
  62. firstProcessorNo
  63. ^1
  64. !
  65. lastProcessorNo
  66. ^processors size
  67. !
  68. processorNo: aNumber
  69. ^processors at: aNumber
  70. !
  71. processors: anArray
  72. processors := anArray
  73. ! !
  74. !TrappedProcessingChain methodsFor: 'action'!
  75. forSnapshot: aSnapshot andBrush: aTagBrush
  76. | toViewCarrier toModelCarrier |
  77. toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
  78. toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
  79. processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].
  80. toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]
  81. ! !
  82. !TrappedProcessingChain class methodsFor: 'instance creation'!
  83. new: anArray
  84. (anArray anySatisfy: [ :each | each isExpectingModelData ])
  85. ifFalse: [ anArray add: self dataTerminator ]
  86. ifTrue: [ anArray addFirst: self blackboardReaderWriter ].
  87. ^self new
  88. processors: anArray;
  89. yourself
  90. !
  91. newFromProcessorSpecs: anArray
  92. ^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each asTrapProcSendTo: TrappedProcessor ])
  93. ! !
  94. !TrappedProcessingChain class methodsFor: 'private'!
  95. blackboardReaderWriter
  96. ^TrappedProcessorBlackboard new
  97. !
  98. dataTerminator
  99. ^TrappedProcessorTerminator new
  100. ! !
  101. Object subclass: #TrappedProcessor
  102. instanceVariableNames: ''
  103. package: 'Trapped-Frontend'!
  104. !TrappedProcessor commentStamp!
  105. I am a processing step in TrappedProcessingChain.
  106. I am stateless flyweight (aka servant)
  107. and will get all necessary data as arguments in API calls.
  108. My public API is:
  109. - installToView:toModel:
  110. This gets two TrappedDataCarriers set up without actual data
  111. and at the beginning of their chains. It should do one-time
  112. installation task needed (install event handlers etc.).
  113. To start a chain, do: dataCarrier copy value: data; proceed.
  114. - toView:
  115. This performs transformation of TrappedDataCarrier on its way from model to view.
  116. Should call aDataCarrier proceed to proceed to subsequent step.
  117. - toModel:
  118. This performs transformation of TrappedDataCarrier on its way from view to model.
  119. Should call aDataCarrier proceed to proceed to subsequent step.!
  120. !TrappedProcessor methodsFor: 'data transformation'!
  121. toModel: aDataCarrier
  122. "by default, proceed"
  123. aDataCarrier proceed
  124. !
  125. toView: aDataCarrier
  126. "by default, proceed"
  127. aDataCarrier proceed
  128. ! !
  129. !TrappedProcessor methodsFor: 'installation'!
  130. installToView: aDataCarrier toModel: anotherDataCarrier
  131. "by default, do nothing"
  132. ! !
  133. !TrappedProcessor methodsFor: 'testing'!
  134. isExpectingModelData
  135. ^false
  136. ! !
  137. !TrappedProcessor class methodsFor: 'factory'!
  138. contents
  139. ^TrappedProcessorContents new
  140. ! !
  141. TrappedProcessor subclass: #TrappedDataExpectingProcessor
  142. instanceVariableNames: ''
  143. package: 'Trapped-Frontend'!
  144. !TrappedDataExpectingProcessor commentStamp!
  145. I answer true to isExpectingModelData and serve as a base class
  146. for processor that present / change model data.
  147. When at least one of my instances is present in the chain,
  148. automatic databinding processor is added at the beginning
  149. (the data-binding scenario); otherwise, the chain
  150. is run immediately with true as data (run-once scenario).!
  151. !TrappedDataExpectingProcessor methodsFor: 'testing'!
  152. isExpectingModelData
  153. ^true
  154. ! !
  155. TrappedDataExpectingProcessor subclass: #TrappedProcessorContents
  156. instanceVariableNames: ''
  157. package: 'Trapped-Frontend'!
  158. !TrappedProcessorContents commentStamp!
  159. I put data into target via contents: in toView:!
  160. !TrappedProcessorContents methodsFor: 'data transformation'!
  161. toView: aDataCarrier
  162. aDataCarrier toTargetContents
  163. ! !
  164. TrappedProcessor subclass: #TrappedProcessorBlackboard
  165. instanceVariableNames: ''
  166. package: 'Trapped-Frontend'!
  167. !TrappedProcessorBlackboard commentStamp!
  168. I am used internally to fetch data from blackboard
  169. or write it back.
  170. I am added to the beginning of the chain
  171. when the chain contains at least one element
  172. that isExpectingModelData (see TrappedDataExpectingProcessor).!
  173. !TrappedProcessorBlackboard methodsFor: 'data transformation'!
  174. toModel: aDataCarrier
  175. aDataCarrier modifyTarget
  176. ! !
  177. !TrappedProcessorBlackboard methodsFor: 'installation'!
  178. installToView: aDataCarrier toModel: anotherDataCarrier
  179. | snap |
  180. snap := anotherDataCarrier target.
  181. snap watch: [ :data |
  182. (aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].
  183. snap do: [ aDataCarrier copy value: data; proceed ] ].
  184. aDataCarrier value: false
  185. ! !
  186. TrappedProcessor subclass: #TrappedProcessorTerminator
  187. instanceVariableNames: ''
  188. package: 'Trapped-Frontend'!
  189. !TrappedProcessorTerminator commentStamp!
  190. I do not proceed in toView:.
  191. I am added automatically to end of chain when it does not contain
  192. any element that isExpectingModelData (see TrappedDataExpectingProcessor).!
  193. !TrappedProcessorTerminator methodsFor: 'data transformation'!
  194. toView: aDataCarrier
  195. "stop"
  196. ! !
  197. Object subclass: #TrappedSingleton
  198. instanceVariableNames: ''
  199. package: 'Trapped-Frontend'!
  200. !TrappedSingleton methodsFor: 'action'!
  201. start: args
  202. ^ self subclassResponsibility
  203. ! !
  204. TrappedSingleton class instanceVariableNames: 'current'!
  205. !TrappedSingleton class methodsFor: 'accessing'!
  206. current
  207. ^ current ifNil: [ current := self new ]
  208. ! !
  209. !TrappedSingleton class methodsFor: 'action'!
  210. start: args
  211. self current start: args
  212. ! !
  213. TrappedSingleton subclass: #Trapped
  214. instanceVariableNames: 'registry'
  215. package: 'Trapped-Frontend'!
  216. !Trapped methodsFor: 'accessing'!
  217. byName: aString
  218. ^ registry at: aString
  219. !
  220. register: aListKeyedEntity
  221. self register: aListKeyedEntity name: aListKeyedEntity class name
  222. !
  223. register: aListKeyedEntity name: aString
  224. registry at: aString put: aListKeyedEntity
  225. ! !
  226. !Trapped methodsFor: 'action'!
  227. start: args
  228. args do: [ :each | self register: each ].
  229. self injectToElement: document
  230. ! !
  231. !Trapped methodsFor: 'initialization'!
  232. initialize
  233. super initialize.
  234. registry := #{}.
  235. ! !
  236. !Trapped methodsFor: 'private'!
  237. cloneAndInject: anObject
  238. ^anObject asJQuery clone
  239. each: [ :i :each | self injectToElement: each ];
  240. get: 0
  241. !
  242. descend: anArray snapshotDo: aBlock
  243. | tpsc |
  244. tpsc := TrappedPathStack current.
  245. tpsc append: anArray do: [
  246. | path model |
  247. path := tpsc elements copy.
  248. model := self byName: path first.
  249. aBlock value: (TrappedSnapshot new path: path model: model)
  250. ]
  251. !
  252. injectToChildren: anElement
  253. | child |
  254. child := anElement firstChild.
  255. [ child isNil ] whileFalse: [ self injectToElement: child. child := child nextSibling ]
  256. !
  257. injectToElement: anElement
  258. | jq |
  259. jq := anElement asJQuery.
  260. (jq attr: 'data-trap') ifNotNil: [ :attr |
  261. jq removeAttr: 'data-trap'.
  262. (Trapped parse: attr) do: [ :rule |
  263. (HTMLCanvas onJQuery: jq) root trap: rule first processors: (rule at: 2 ifAbsent: [#()]) ] ].
  264. self injectToChildren: anElement
  265. ! !
  266. !Trapped class methodsFor: 'parsing'!
  267. parse: aString
  268. ^ (aString tokenize: '.') collect: [ :rule |
  269. (rule tokenize: ':') collect: [ :message |
  270. Lyst parse: message ] ]
  271. ! !
  272. !Trapped class methodsFor: 'private'!
  273. loop: aSequenceableCollection before: aNode do: aBlock
  274. aSequenceableCollection withIndexDo: [ :item :i |
  275. | env |
  276. "env := document createDocumentFragment."
  277. env := document createElement: 'ins'.
  278. {i} trapDescend: [ (HTMLCanvas onJQuery: env asJQuery) root with: aBlock ].
  279. "aNode parentNode insertBefore: env reference: aNode"
  280. (Array streamContents: [ :str |
  281. | child |
  282. child := env firstChild.
  283. [ child isNil ] whileFalse: [
  284. str nextPut: child.
  285. child := child nextSibling ]])
  286. do: [ :each | aNode parentNode insertBefore: each reference: aNode ]
  287. ]
  288. !
  289. loop: aSequenceableCollection between: aTagBrush and: anotherTagBrush do: aBlock
  290. (aTagBrush asJQuery nextUntil: anotherTagBrush element) remove.
  291. aSequenceableCollection ifNotNil: [
  292. self loop: aSequenceableCollection before: anotherTagBrush element do: aBlock
  293. ]
  294. ! !
  295. TrappedSingleton subclass: #TrappedPathStack
  296. instanceVariableNames: 'elements'
  297. package: 'Trapped-Frontend'!
  298. !TrappedPathStack methodsFor: 'accessing'!
  299. elements
  300. ^elements
  301. ! !
  302. !TrappedPathStack methodsFor: 'descending'!
  303. append: anArray do: aBlock
  304. self with: elements, anArray do: aBlock
  305. !
  306. with: anArray do: aBlock
  307. | old |
  308. old := elements.
  309. [ elements := anArray.
  310. aBlock value ] ensure: [ elements := old ]
  311. ! !
  312. !TrappedPathStack methodsFor: 'initialization'!
  313. initialize
  314. super initialize.
  315. elements := #().
  316. ! !
  317. Object subclass: #TrappedSnapshot
  318. instanceVariableNames: 'path model'
  319. package: 'Trapped-Frontend'!
  320. !TrappedSnapshot methodsFor: 'accessing'!
  321. model
  322. ^model
  323. !
  324. path
  325. ^path
  326. !
  327. path: anArray model: aTrappedMW
  328. path := anArray.
  329. model := aTrappedMW
  330. ! !
  331. !TrappedSnapshot methodsFor: 'action'!
  332. do: aBlock
  333. TrappedPathStack current with: path do: [ aBlock value: model ]
  334. !
  335. modify: aBlock
  336. self model modify: self path allButFirst do: aBlock
  337. !
  338. read: aBlock
  339. self model read: self path allButFirst do: aBlock
  340. !
  341. watch: aBlock
  342. self model watch: self path allButFirst do: aBlock
  343. ! !
  344. !Array methodsFor: '*Trapped-Frontend'!
  345. asTrapProcSendTo: anObject
  346. | selector args |
  347. selector := ''.
  348. args := #().
  349. self withIndexDo: [ :element :index | index odd
  350. ifTrue: [ selector := selector, element ]
  351. ifFalse: [ selector := selector, ':'. args add: element ] ].
  352. ^anObject perform: selector withArguments: args
  353. !
  354. trapDescend: aBlock
  355. Trapped current descend: self snapshotDo: aBlock
  356. ! !
  357. !HTMLCanvas methodsFor: '*Trapped-Frontend'!
  358. trapIter: path do: aBlock
  359. self with: [ :html | (html tag: 'script') at: 'type' put: 'application/x-beacon'; trapIter: path after: aBlock ]
  360. ! !
  361. !Object methodsFor: '*Trapped-Frontend'!
  362. asTrapProcSendTo: anObject
  363. self error: 'Trapped cannot use processor descriptor of ', self class name, ' type.'
  364. ! !
  365. !String methodsFor: '*Trapped-Frontend'!
  366. asTrapProcSendTo: anObject
  367. ^anObject perform: self
  368. ! !
  369. !TagBrush methodsFor: '*Trapped-Frontend'!
  370. trap: path
  371. self trap: path processors: #()
  372. !
  373. trap: path processors: anArray
  374. path trapDescend: [ :snap |
  375. (TrappedProcessingChain newFromProcessorSpecs: anArray)
  376. forSnapshot: snap andBrush: self ]
  377. !
  378. trap: path read: aBlock
  379. path trapDescend: [ :snap |
  380. snap watch: [ :data |
  381. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].
  382. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  383. ]
  384. ]
  385. !
  386. trapGuard: anArray contents: aBlock
  387. #() trapDescend: [ :snap |
  388. | shown |
  389. shown := nil.
  390. self trap: anArray read: [ :gdata |
  391. | sanitized |
  392. sanitized := gdata ifNil: [ false ].
  393. shown = sanitized ifFalse: [
  394. shown := sanitized.
  395. shown
  396. ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
  397. ifFalse: [ self asJQuery hide; empty ] ] ] ]
  398. !
  399. trapIter: path after: aBlock
  400. | end |
  401. end := TagBrush fromJQuery: ('<script type="application/x-beacon" />' asJQuery) canvas: canvas.
  402. self element parentNode insertBefore: end element reference: self element nextSibling.
  403. self trap: path read: [ :model |
  404. Trapped loop: model between: self and: end do: aBlock.
  405. ]
  406. ! !