Trapped-Frontend.st 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. Smalltalk current createPackage: 'Trapped-Frontend'!
  2. Object subclass: #TrappedDataCarrier
  3. instanceVariableNames: 'target model chain'
  4. package: 'Trapped-Frontend'!
  5. !TrappedDataCarrier methodsFor: 'accessing'!
  6. chain: aDataChain
  7. chain := aDataChain
  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. !TrappedDataCarrier methodsFor: 'action'!
  22. modifyTarget
  23. self target modify: [ self value ]
  24. !
  25. toTargetAttr: aString
  26. self target asJQuery attr: aString put: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
  27. !
  28. toTargetContents
  29. self target contents: self value
  30. !
  31. toTargetValue
  32. self target asJQuery val: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
  33. ! !
  34. !TrappedDataCarrier class methodsFor: 'not yet classified'!
  35. on: aDataChain target: anObject
  36. ^self new
  37. chain: aDataChain;
  38. target: anObject;
  39. yourself
  40. ! !
  41. TrappedDataCarrier subclass: #TrappedDataCarrierToModel
  42. instanceVariableNames: 'index'
  43. package: 'Trapped-Frontend'!
  44. !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
  45. proceed
  46. index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
  47. (chain processorNo: index) toModel: self
  48. ! !
  49. TrappedDataCarrier subclass: #TrappedDataCarrierToView
  50. instanceVariableNames: 'index'
  51. package: 'Trapped-Frontend'!
  52. !TrappedDataCarrierToView methodsFor: 'not yet classified'!
  53. proceed
  54. index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
  55. (chain processorNo: index) toView: self
  56. ! !
  57. Object subclass: #TrappedDataChain
  58. instanceVariableNames: 'processors'
  59. package: 'Trapped-Frontend'!
  60. !TrappedDataChain methodsFor: 'accessing'!
  61. firstProcessorNo
  62. ^1
  63. !
  64. lastProcessorNo
  65. ^processors size
  66. !
  67. processorNo: aNumber
  68. ^processors at: aNumber
  69. !
  70. processors: anArray
  71. processors := anArray
  72. ! !
  73. !TrappedDataChain methodsFor: 'action'!
  74. forSnapshot: aSnapshot andBrush: aTagBrush
  75. | toViewCarrier toModelCarrier |
  76. toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
  77. toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
  78. processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ]
  79. ! !
  80. !TrappedDataChain class methodsFor: 'instance creation'!
  81. new: anArray
  82. ^self new
  83. processors: { self blackboardReaderWriter }, anArray;
  84. yourself
  85. !
  86. newFromProcessorSpecs: anArray
  87. ^self new: (anArray collect: [ :each | TrappedProcessor perform: each ])
  88. ! !
  89. !TrappedDataChain class methodsFor: 'private'!
  90. blackboardReaderWriter
  91. ^TrappedProcessorBlackboard new
  92. ! !
  93. Widget subclass: #TrappedDumbView
  94. instanceVariableNames: ''
  95. package: 'Trapped-Frontend'!
  96. !TrappedDumbView commentStamp!
  97. I just read and show an actual path.!
  98. !TrappedDumbView methodsFor: 'rendering'!
  99. renderOn: html
  100. html root trap: #()
  101. ! !
  102. Object subclass: #TrappedProcessor
  103. instanceVariableNames: ''
  104. package: 'Trapped-Frontend'!
  105. !TrappedProcessor commentStamp!
  106. I process data in TrappedDataChain.
  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 TrappedDataToken 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 class methodsFor: 'factory'!
  135. contents
  136. ^TrappedProcessorContents new
  137. !
  138. inputChecked
  139. ^TrappedProcessorInputChecked new
  140. !
  141. inputValue
  142. ^TrappedProcessorInputValue new
  143. !
  144. whenClicked
  145. ^TrappedProcessorWhenClicked new
  146. !
  147. whenSubmitted
  148. ^TrappedProcessorWhenSubmitted new
  149. ! !
  150. TrappedProcessor subclass: #TrappedProcessorBlackboard
  151. instanceVariableNames: ''
  152. package: 'Trapped-Frontend'!
  153. !TrappedProcessorBlackboard commentStamp!
  154. I am used internally to fetch data from blackboard
  155. or write it back.!
  156. !TrappedProcessorBlackboard methodsFor: 'data transformation'!
  157. toModel: aDataCarrier
  158. aDataCarrier modifyTarget
  159. ! !
  160. !TrappedProcessorBlackboard methodsFor: 'installation'!
  161. installToView: aDataCarrier toModel: anotherDataCarrier
  162. | snap |
  163. snap := anotherDataCarrier target.
  164. snap watch: [ :data |
  165. (aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  166. snap do: [ aDataCarrier copy value: data; proceed ] ]
  167. ! !
  168. TrappedProcessor subclass: #TrappedProcessorContents
  169. instanceVariableNames: ''
  170. package: 'Trapped-Frontend'!
  171. !TrappedProcessorContents commentStamp!
  172. I put data into target via contents: in toView:!
  173. !TrappedProcessorContents methodsFor: 'data transformation'!
  174. toView: aDataCarrier
  175. aDataCarrier toTargetContents
  176. ! !
  177. TrappedProcessor subclass: #TrappedProcessorInputChecked
  178. instanceVariableNames: ''
  179. package: 'Trapped-Frontend'!
  180. !TrappedProcessorInputChecked commentStamp!
  181. I bind to checkbox checked attribute.!
  182. !TrappedProcessorInputChecked methodsFor: 'data transformation'!
  183. toView: aDataCarrier
  184. aDataCarrier toTargetAttr: 'checked'
  185. ! !
  186. !TrappedProcessorInputChecked methodsFor: 'installation'!
  187. installToView: aDataCarrier toModel: anotherDataCarrier
  188. | brush |
  189. brush := aDataCarrier target.
  190. brush onChange: [ anotherDataCarrier copy value: (brush asJQuery attr: 'checked') notNil; proceed ]
  191. ! !
  192. TrappedProcessor subclass: #TrappedProcessorInputValue
  193. instanceVariableNames: ''
  194. package: 'Trapped-Frontend'!
  195. !TrappedProcessorInputValue commentStamp!
  196. I bind to input value.!
  197. !TrappedProcessorInputValue methodsFor: 'data transformation'!
  198. toView: aDataCarrier
  199. aDataCarrier toTargetValue
  200. ! !
  201. !TrappedProcessorInputValue methodsFor: 'installation'!
  202. installToView: aDataCarrier toModel: anotherDataCarrier
  203. | brush |
  204. brush := aDataCarrier target.
  205. brush onChange: [ anotherDataCarrier copy value: brush asJQuery val; proceed ]
  206. ! !
  207. TrappedProcessor subclass: #TrappedProcessorWhenClicked
  208. instanceVariableNames: ''
  209. package: 'Trapped-Frontend'!
  210. !TrappedProcessorWhenClicked commentStamp!
  211. I bind to an element and send true to blackboard when clicked.!
  212. !TrappedProcessorWhenClicked methodsFor: 'installation'!
  213. installToView: aDataCarrier toModel: anotherDataCarrier
  214. aDataCarrier target onClick: [ anotherDataCarrier copy value: true; proceed. false ]
  215. ! !
  216. TrappedProcessor subclass: #TrappedProcessorWhenSubmitted
  217. instanceVariableNames: ''
  218. package: 'Trapped-Frontend'!
  219. !TrappedProcessorWhenSubmitted commentStamp!
  220. I bind to a form and send true to blackboard when submitted.!
  221. !TrappedProcessorWhenSubmitted methodsFor: 'installation'!
  222. installToView: aDataCarrier toModel: anotherDataCarrier
  223. aDataCarrier target onSubmit: [ anotherDataCarrier copy value: true; proceed. false ]
  224. ! !
  225. Object subclass: #TrappedSingleton
  226. instanceVariableNames: ''
  227. package: 'Trapped-Frontend'!
  228. !TrappedSingleton methodsFor: 'action'!
  229. start: args
  230. ^ self subclassResponsibility
  231. ! !
  232. TrappedSingleton class instanceVariableNames: 'current'!
  233. !TrappedSingleton class methodsFor: 'accessing'!
  234. current
  235. ^ current ifNil: [ current := self new ]
  236. ! !
  237. !TrappedSingleton class methodsFor: 'action'!
  238. start: args
  239. self current start: args
  240. ! !
  241. TrappedSingleton subclass: #Trapped
  242. instanceVariableNames: 'registry'
  243. package: 'Trapped-Frontend'!
  244. !Trapped methodsFor: 'accessing'!
  245. byName: aString
  246. ^ registry at: aString
  247. !
  248. register: aListKeyedEntity
  249. self register: aListKeyedEntity name: aListKeyedEntity class name
  250. !
  251. register: aListKeyedEntity name: aString
  252. registry at: aString put: aListKeyedEntity
  253. ! !
  254. !Trapped methodsFor: 'action'!
  255. descend: anArray snapshotDo: aBlock
  256. | tpsc |
  257. tpsc := TrappedPathStack current.
  258. tpsc append: anArray do: [
  259. | path model |
  260. path := tpsc elements copy.
  261. model := self byName: path first.
  262. aBlock value: (TrappedSnapshot new path: path model: model)
  263. ]
  264. !
  265. start: args
  266. args do: [ :each | self register: each ].
  267. '[data-trap]' asJQuery each: [ :index :elem |
  268. | trap jq viewName modelName tokens path |
  269. jq := elem asJQuery.
  270. trap := jq attr: 'data-trap'.
  271. tokens := trap tokenize: ':'.
  272. tokens size = 1 ifTrue: [ tokens := { 'TrappedDumbView' }, tokens ].
  273. viewName := tokens first.
  274. tokens := (tokens second tokenize: ' ') select: [ :each | each notEmpty ].
  275. modelName := tokens first.
  276. path := Trapped parse: tokens allButFirst.
  277. { modelName }, path trapDescend: [(Smalltalk current at: viewName) new appendToJQuery: jq].
  278. ]
  279. ! !
  280. !Trapped methodsFor: 'initialization'!
  281. initialize
  282. super initialize.
  283. registry := #{}.
  284. ! !
  285. !Trapped class methodsFor: 'accessing'!
  286. parse: anArray
  287. ^anArray collect: [ :each |
  288. | asNum |
  289. asNum := each asNumber.
  290. asNum = asNum ifTrue: [ asNum ] ifFalse: [
  291. each first = '#' ifTrue: [ { each allButFirst } ] ifFalse: [ each ]]]
  292. ! !
  293. !Trapped class methodsFor: 'private'!
  294. envelope: envelope loop: model before: endjq tag: aSymbol do: aBlock
  295. | envjq |
  296. envjq := envelope asJQuery.
  297. model withIndexDo: [ :item :i |
  298. envelope with: [ :html | (html perform: aSymbol) trap: {i} read: aBlock ].
  299. envjq children detach insertBefore: endjq.
  300. ].
  301. envjq remove
  302. !
  303. loop: model between: start and: end tag: aSymbol do: aBlock
  304. (start asJQuery nextUntil: end element) remove.
  305. start with: [ :html | model ifNotNil: [
  306. self envelope: html div loop: model before: end asJQuery tag: aSymbol do: aBlock
  307. ]]
  308. ! !
  309. TrappedSingleton subclass: #TrappedPathStack
  310. instanceVariableNames: 'elements'
  311. package: 'Trapped-Frontend'!
  312. !TrappedPathStack methodsFor: 'accessing'!
  313. elements
  314. ^elements
  315. ! !
  316. !TrappedPathStack methodsFor: 'descending'!
  317. append: anArray do: aBlock
  318. self with: elements, anArray do: aBlock
  319. !
  320. with: anArray do: aBlock
  321. | old |
  322. old := elements.
  323. [ elements := anArray.
  324. aBlock value ] ensure: [ elements := old ]
  325. ! !
  326. !TrappedPathStack methodsFor: 'initialization'!
  327. initialize
  328. super initialize.
  329. elements := #().
  330. ! !
  331. Object subclass: #TrappedSnapshot
  332. instanceVariableNames: 'path model'
  333. package: 'Trapped-Frontend'!
  334. !TrappedSnapshot methodsFor: 'accessing'!
  335. model
  336. ^model
  337. !
  338. path
  339. ^path
  340. !
  341. path: anArray model: aTrappedMW
  342. path := anArray.
  343. model := aTrappedMW
  344. ! !
  345. !TrappedSnapshot methodsFor: 'action'!
  346. do: aBlock
  347. TrappedPathStack current with: path do: [ aBlock value: model ]
  348. !
  349. modify: aBlock
  350. self model modify: self path allButFirst do: aBlock
  351. !
  352. watch: aBlock
  353. self model watch: self path allButFirst do: aBlock
  354. ! !
  355. !Array methodsFor: '*Trapped-Frontend'!
  356. trapDescend: aBlock
  357. Trapped current descend: self snapshotDo: aBlock
  358. ! !
  359. !HTMLCanvas methodsFor: '*Trapped-Frontend'!
  360. trapIter: path tag: aSymbol do: aBlock
  361. | start end |
  362. self with: [ :html | start := html script. end := html script ].
  363. start trap: path read: [ :model |
  364. Trapped loop: model between: start and: end tag: aSymbol do: aBlock.
  365. ]
  366. ! !
  367. !TagBrush methodsFor: '*Trapped-Frontend'!
  368. trap: path
  369. self trap: path processors: #(contents)
  370. !
  371. trap: path processors: anArray
  372. path trapDescend: [ :snap |
  373. (TrappedDataChain newFromProcessorSpecs: anArray)
  374. forSnapshot: snap andBrush: self ]
  375. !
  376. trap: path read: aBlock
  377. path trapDescend: [ :snap |
  378. snap watch: [ :data |
  379. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  380. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  381. ]
  382. ]
  383. !
  384. trapGuard: anArray contents: aBlock
  385. #() trapDescend: [ :snap |
  386. | shown |
  387. shown := nil.
  388. self trap: anArray read: [ :gdata |
  389. | sanitized |
  390. sanitized := gdata ifNil: [ false ].
  391. shown = sanitized ifFalse: [
  392. shown := sanitized.
  393. shown
  394. ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
  395. ifFalse: [ self asJQuery hide; empty ] ] ] ]
  396. ! !