Trapped-Frontend.st 13 KB

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