Trapped-Frontend.st 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. Smalltalk current createPackage: 'Trapped-Frontend'!
  2. Object subclass: #TrappedBinder
  3. instanceVariableNames: 'brush'
  4. package: 'Trapped-Frontend'!
  5. !TrappedBinder methodsFor: 'accessing'!
  6. brush: aTagBrush
  7. brush := aTagBrush
  8. ! !
  9. !TrappedBinder methodsFor: 'action'!
  10. installFor: path
  11. brush trap: path read: self showBlock
  12. !
  13. showBlock
  14. ^[ :model | brush empty; with: (model ifNil: [[]]) ]
  15. ! !
  16. !TrappedBinder methodsFor: 'converting'!
  17. prim: anObject
  18. <return anObject.valueOf()>
  19. ! !
  20. TrappedBinder subclass: #TrappedCheckedBinder
  21. instanceVariableNames: ''
  22. package: 'Trapped-Frontend'!
  23. !TrappedCheckedBinder methodsFor: 'action'!
  24. installFor: path
  25. super installFor: path.
  26. path trapDescend: [ :snap |
  27. brush onChange: [ snap modify: [
  28. (brush asJQuery attr: 'checked') notNil
  29. ]]
  30. ]
  31. !
  32. showBlock
  33. ^[ :model | brush asJQuery attr: 'checked' put: (model ifNotNil: [ self prim: model ] ifNil: [ false ]) ]
  34. ! !
  35. TrappedBinder subclass: #TrappedValBinder
  36. instanceVariableNames: ''
  37. package: 'Trapped-Frontend'!
  38. !TrappedValBinder methodsFor: 'action'!
  39. installFor: path
  40. super installFor: path.
  41. path trapDescend: [ :snap |
  42. brush onChange: [ snap modify: [
  43. brush asJQuery val
  44. ]]
  45. ]
  46. !
  47. showBlock
  48. ^[ :model | brush asJQuery val: (model ifNotNil: [self prim: model] ifNil: [[]]) ]
  49. ! !
  50. Object subclass: #TrappedDataCarrier
  51. instanceVariableNames: 'target model chain'
  52. package: 'Trapped-Frontend'!
  53. !TrappedDataCarrier methodsFor: 'accessing'!
  54. chain: aDataChain
  55. chain := aDataChain
  56. !
  57. target
  58. ^target
  59. !
  60. target: anObject
  61. target := anObject
  62. !
  63. value
  64. ^model
  65. !
  66. value: anObject
  67. model := anObject
  68. ! !
  69. !TrappedDataCarrier methodsFor: 'action'!
  70. modifyTarget
  71. self target modify: [ self value ]
  72. !
  73. toTargetContents
  74. self target contents: self value
  75. ! !
  76. !TrappedDataCarrier class methodsFor: 'not yet classified'!
  77. on: aDataChain target: anObject
  78. ^self new
  79. chain: aDataChain;
  80. target: anObject;
  81. yourself
  82. ! !
  83. TrappedDataCarrier subclass: #TrappedDataCarrierToModel
  84. instanceVariableNames: 'index'
  85. package: 'Trapped-Frontend'!
  86. !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
  87. proceed
  88. index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
  89. (chain processorNo: index) toModel: self
  90. ! !
  91. TrappedDataCarrier subclass: #TrappedDataCarrierToView
  92. instanceVariableNames: 'index'
  93. package: 'Trapped-Frontend'!
  94. !TrappedDataCarrierToView methodsFor: 'not yet classified'!
  95. proceed
  96. index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
  97. (chain processorNo: index) toView: self
  98. ! !
  99. Object subclass: #TrappedDataChain
  100. instanceVariableNames: 'processors'
  101. package: 'Trapped-Frontend'!
  102. !TrappedDataChain methodsFor: 'accessing'!
  103. firstProcessorNo
  104. ^1
  105. !
  106. lastProcessorNo
  107. ^processors size
  108. !
  109. processorNo: aNumber
  110. ^processors at: aNumber
  111. !
  112. processors: anArray
  113. processors := anArray
  114. ! !
  115. !TrappedDataChain methodsFor: 'action'!
  116. forSnapshot: aSnapshot andBrush: aTagBrush
  117. | toViewCarrier toModelCarrier |
  118. toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
  119. toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
  120. processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ]
  121. ! !
  122. !TrappedDataChain class methodsFor: 'instance creation'!
  123. new: anArray
  124. ^self new
  125. processors: { self blackboardReaderWriter }, anArray;
  126. yourself
  127. !
  128. newFromProcessorNames: anArray
  129. ^self new: (anArray collect: [ :each | TrappedProcessor perform: each ])
  130. ! !
  131. !TrappedDataChain class methodsFor: 'private'!
  132. blackboardReaderWriter
  133. ^TrappedProcessorBlackboard new
  134. ! !
  135. Widget subclass: #TrappedDumbView
  136. instanceVariableNames: ''
  137. package: 'Trapped-Frontend'!
  138. !TrappedDumbView commentStamp!
  139. I just read and show an actual path.!
  140. !TrappedDumbView methodsFor: 'rendering'!
  141. renderOn: html
  142. html root trap: #()
  143. ! !
  144. Object subclass: #TrappedProcessor
  145. instanceVariableNames: ''
  146. package: 'Trapped-Frontend'!
  147. !TrappedProcessor commentStamp!
  148. I process data in TrappedDataChain.
  149. I am stateless flyweight (aka servant)
  150. and will get all necessary data as arguments in API calls.
  151. My public API is:
  152. - installToView:toModel:
  153. This gets two TrappedDataCarriers set up without actual data
  154. and at the beginning of their chains. It should do one-time
  155. installation task needed (install event handlers etc.).
  156. To start a chain, do: dataCarrier copy value: data; proceed.
  157. - toView:
  158. This performs transformation of TrappedDataCarrier on its way from model to view.
  159. Should call aDataCarrier proceed to proceed to subsequent step.
  160. - toModel:
  161. This performs transformation of TrappedDataToken on its way from view to model.
  162. Should call aDataCarrier proceed to proceed to subsequent step.!
  163. !TrappedProcessor methodsFor: 'data transformation'!
  164. toModel: aDataCarrier
  165. "by default, proceed"
  166. aDataCarrier proceed
  167. !
  168. toView: aDataCarrier
  169. "by default, proceed"
  170. aDataCarrier proceed
  171. ! !
  172. !TrappedProcessor methodsFor: 'installation'!
  173. installToView: aDataCarrier toModel: anotherDataCarrier
  174. "by default, do nothing"
  175. ! !
  176. !TrappedProcessor class methodsFor: 'factory'!
  177. contents
  178. ^TrappedProcessorContents new
  179. ! !
  180. TrappedProcessor subclass: #TrappedProcessorBlackboard
  181. instanceVariableNames: ''
  182. package: 'Trapped-Frontend'!
  183. !TrappedProcessorBlackboard commentStamp!
  184. I am used internally to fetch data from blackboard
  185. or write it back.!
  186. !TrappedProcessorBlackboard methodsFor: 'data transformation'!
  187. toModel: aDataCarrier
  188. aDataCarrier modifyTarget
  189. ! !
  190. !TrappedProcessorBlackboard methodsFor: 'installation'!
  191. installToView: aDataCarrier toModel: anotherDataCarrier
  192. | snap |
  193. snap := anotherDataCarrier target.
  194. snap watch: [ :data |
  195. (aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  196. snap do: [ aDataCarrier copy value: data; proceed ] ]
  197. ! !
  198. TrappedProcessor subclass: #TrappedProcessorContents
  199. instanceVariableNames: ''
  200. package: 'Trapped-Frontend'!
  201. !TrappedProcessorContents commentStamp!
  202. I put data into target via contents: in toView:!
  203. !TrappedProcessorContents methodsFor: 'data transformation'!
  204. toView: aDataCarrier
  205. aDataCarrier toTargetContents
  206. ! !
  207. Object subclass: #TrappedSingleton
  208. instanceVariableNames: ''
  209. package: 'Trapped-Frontend'!
  210. !TrappedSingleton methodsFor: 'action'!
  211. start: args
  212. ^ self subclassResponsibility
  213. ! !
  214. TrappedSingleton class instanceVariableNames: 'current'!
  215. !TrappedSingleton class methodsFor: 'accessing'!
  216. current
  217. ^ current ifNil: [ current := self new ]
  218. ! !
  219. !TrappedSingleton class methodsFor: 'action'!
  220. start: args
  221. self current start: args
  222. ! !
  223. TrappedSingleton subclass: #Trapped
  224. instanceVariableNames: 'registry'
  225. package: 'Trapped-Frontend'!
  226. !Trapped methodsFor: 'accessing'!
  227. byName: aString
  228. ^ registry at: aString
  229. !
  230. register: aListKeyedEntity
  231. self register: aListKeyedEntity name: aListKeyedEntity class name
  232. !
  233. register: aListKeyedEntity name: aString
  234. registry at: aString put: aListKeyedEntity
  235. ! !
  236. !Trapped methodsFor: 'action'!
  237. descend: anArray snapshotDo: aBlock
  238. | tpsc |
  239. tpsc := TrappedPathStack current.
  240. tpsc append: anArray do: [
  241. | path model |
  242. path := tpsc elements copy.
  243. model := self byName: path first.
  244. aBlock value: (TrappedSnapshot new path: path model: model)
  245. ]
  246. !
  247. start: args
  248. args do: [ :each | self register: each ].
  249. '[data-trap]' asJQuery each: [ :index :elem |
  250. | trap jq viewName modelName tokens path |
  251. jq := elem asJQuery.
  252. trap := jq attr: 'data-trap'.
  253. tokens := trap tokenize: ':'.
  254. tokens size = 1 ifTrue: [ tokens := { 'TrappedDumbView' }, tokens ].
  255. viewName := tokens first.
  256. tokens := (tokens second tokenize: ' ') select: [ :each | each notEmpty ].
  257. modelName := tokens first.
  258. path := Trapped parse: tokens allButFirst.
  259. { modelName }, path trapDescend: [(Smalltalk current at: viewName) new appendToJQuery: jq].
  260. ]
  261. ! !
  262. !Trapped methodsFor: 'binders'!
  263. binder: aTagBrush
  264. "Prototype; will select based on tag etc."
  265. | binder tag |
  266. tag := aTagBrush element nodeName.
  267. tag = 'INPUT' ifTrue: [
  268. | type |
  269. type := aTagBrush asJQuery attr: 'type'.
  270. type = 'checkbox' ifTrue: [ binder := TrappedCheckedBinder new ].
  271. type = 'text' ifTrue: [ binder := TrappedValBinder new ]
  272. ].
  273. binder ifNil: [ binder := TrappedBinder new ].
  274. ^ binder brush: aTagBrush; yourself
  275. ! !
  276. !Trapped methodsFor: 'initialization'!
  277. initialize
  278. super initialize.
  279. registry := #{}.
  280. ! !
  281. !Trapped class methodsFor: 'accessing'!
  282. parse: anArray
  283. ^anArray collect: [ :each |
  284. | asNum |
  285. asNum := each asNumber.
  286. asNum = asNum ifTrue: [ asNum ] ifFalse: [
  287. each first = '#' ifTrue: [ { each allButFirst } ] ifFalse: [ each ]]]
  288. ! !
  289. !Trapped class methodsFor: 'private'!
  290. envelope: envelope loop: model before: endjq tag: aSymbol do: aBlock
  291. | envjq |
  292. envjq := envelope asJQuery.
  293. model withIndexDo: [ :item :i |
  294. envelope with: [ :html | (html perform: aSymbol) trap: {i} read: aBlock ].
  295. envjq children detach insertBefore: endjq.
  296. ].
  297. envjq remove
  298. !
  299. loop: model between: start and: end tag: aSymbol do: aBlock
  300. (start asJQuery nextUntil: end element) remove.
  301. start with: [ :html | model ifNotNil: [
  302. self envelope: html div loop: model before: end asJQuery tag: aSymbol do: aBlock
  303. ]]
  304. ! !
  305. TrappedSingleton subclass: #TrappedPathStack
  306. instanceVariableNames: 'elements'
  307. package: 'Trapped-Frontend'!
  308. !TrappedPathStack methodsFor: 'accessing'!
  309. elements
  310. ^elements
  311. ! !
  312. !TrappedPathStack methodsFor: 'descending'!
  313. append: anArray do: aBlock
  314. self with: elements, anArray do: aBlock
  315. !
  316. with: anArray do: aBlock
  317. | old |
  318. old := elements.
  319. [ elements := anArray.
  320. aBlock value ] ensure: [ elements := old ]
  321. ! !
  322. !TrappedPathStack methodsFor: 'initialization'!
  323. initialize
  324. super initialize.
  325. elements := #().
  326. ! !
  327. Object subclass: #TrappedSnapshot
  328. instanceVariableNames: 'path model'
  329. package: 'Trapped-Frontend'!
  330. !TrappedSnapshot methodsFor: 'accessing'!
  331. model
  332. ^model
  333. !
  334. path
  335. ^path
  336. !
  337. path: anArray model: aTrappedMW
  338. path := anArray.
  339. model := aTrappedMW
  340. ! !
  341. !TrappedSnapshot methodsFor: 'action'!
  342. do: aBlock
  343. TrappedPathStack current with: path do: [ aBlock value: model ]
  344. !
  345. modify: aBlock
  346. self model modify: self path allButFirst do: aBlock
  347. !
  348. watch: aBlock
  349. self model watch: self path allButFirst do: aBlock
  350. ! !
  351. !Array methodsFor: '*Trapped-Frontend'!
  352. trapDescend: aBlock
  353. Trapped current descend: self snapshotDo: aBlock
  354. ! !
  355. !HTMLCanvas methodsFor: '*Trapped-Frontend'!
  356. trapIter: path tag: aSymbol do: aBlock
  357. | start end |
  358. self with: [ :html | start := html script. end := html script ].
  359. start trap: path read: [ :model |
  360. Trapped loop: model between: start and: end tag: aSymbol do: aBlock.
  361. ]
  362. ! !
  363. !TagBrush methodsFor: '*Trapped-Frontend'!
  364. trap: path
  365. self trap: path processors: #(contents)
  366. !
  367. trap: path processors: anArray
  368. path trapDescend: [ :snap |
  369. (TrappedDataChain newFromProcessorNames: anArray)
  370. forSnapshot: snap andBrush: self ]
  371. !
  372. trap: path read: aBlock
  373. path trapDescend: [ :snap |
  374. snap watch: [ :data |
  375. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  376. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  377. ]
  378. ]
  379. !
  380. trapGuard: anArray contents: aBlock
  381. #() trapDescend: [ :snap |
  382. | shown |
  383. shown := nil.
  384. self trap: anArray read: [ :gdata |
  385. | sanitized |
  386. sanitized := gdata ifNil: [ false ].
  387. shown = sanitized ifFalse: [
  388. shown := sanitized.
  389. shown
  390. ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
  391. ifFalse: [ self asJQuery hide; empty ] ] ] ]
  392. ! !