Trapped-Frontend.st 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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: 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. !TrappedDataCarrier methodsFor: 'initialization'!
  22. initialize
  23. super initialize.
  24. model := true
  25. ! !
  26. !TrappedDataCarrier class methodsFor: 'not yet classified'!
  27. on: aProcessingChain target: anObject
  28. ^self new
  29. chain: aProcessingChain;
  30. target: anObject;
  31. yourself
  32. ! !
  33. TrappedDataCarrier subclass: #TrappedDataCarrierToModel
  34. instanceVariableNames: 'index'
  35. package: 'Trapped-Frontend'!
  36. !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
  37. proceed
  38. index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
  39. (chain processorNo: index) toModel: self
  40. ! !
  41. TrappedDataCarrier subclass: #TrappedDataCarrierToView
  42. instanceVariableNames: 'index'
  43. package: 'Trapped-Frontend'!
  44. !TrappedDataCarrierToView methodsFor: 'not yet classified'!
  45. proceed
  46. index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
  47. (chain processorNo: index) toView: self
  48. ! !
  49. Object subclass: #TrappedProcessingChain
  50. instanceVariableNames: 'processors'
  51. package: 'Trapped-Frontend'!
  52. !TrappedProcessingChain methodsFor: 'accessing'!
  53. firstProcessorNo
  54. ^1
  55. !
  56. lastProcessorNo
  57. ^processors size
  58. !
  59. processorNo: aNumber
  60. ^processors at: aNumber
  61. !
  62. processors: anArray
  63. processors := anArray
  64. ! !
  65. !TrappedProcessingChain methodsFor: 'action'!
  66. forSnapshot: aSnapshot andBrush: aTagBrush
  67. | toViewCarrier toModelCarrier |
  68. toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
  69. toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
  70. processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].
  71. toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]
  72. ! !
  73. !TrappedProcessingChain class methodsFor: 'instance creation'!
  74. new: anArray
  75. (anArray detect: [ :each | each isExpectingModelData ] ifNone: [ nil ])
  76. ifNil: [ anArray add: self dataTerminator ]
  77. ifNotNil: [ anArray addFirst: self blackboardReaderWriter ].
  78. ^self new
  79. processors: anArray;
  80. yourself
  81. !
  82. newFromProcessorSpecs: anArray
  83. ^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each isString
  84. ifTrue: [ TrappedProcessor perform: each ]
  85. ifFalse: [
  86. | selector args |
  87. selector := ''.
  88. args := #().
  89. each withIndexDo: [ :element :index | index odd
  90. ifTrue: [ selector := selector, element ]
  91. ifFalse: [ selector := selector, ':'. args add: element ] ].
  92. TrappedProcessor perform: selector withArguments: args ] ])
  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. dataToView: aBlock
  142. ^TrappedProcessorDataAdhoc newToView: aBlock
  143. !
  144. guardContents: anArray
  145. ^TrappedProcessorGuardContents new: anArray
  146. !
  147. guardProc: anArray
  148. ^TrappedProcessorGuardProc new: anArray
  149. !
  150. inputChecked
  151. ^TrappedProcessorInputChecked new
  152. !
  153. inputValue
  154. ^TrappedProcessorInputValue new
  155. !
  156. path
  157. ^TrappedProcessorDescend new
  158. !
  159. signal: aString
  160. ^TrappedProcessorSignal new: aString
  161. !
  162. whenClicked
  163. ^TrappedProcessorWhenClicked new
  164. !
  165. whenSubmitted
  166. ^TrappedProcessorWhenSubmitted new
  167. !
  168. widget: aString
  169. ^TrappedProcessorWidget new: aString
  170. ! !
  171. Object subclass: #TrappedSingleton
  172. instanceVariableNames: ''
  173. package: 'Trapped-Frontend'!
  174. !TrappedSingleton methodsFor: 'action'!
  175. start: args
  176. ^ self subclassResponsibility
  177. ! !
  178. TrappedSingleton class instanceVariableNames: 'current'!
  179. !TrappedSingleton class methodsFor: 'accessing'!
  180. current
  181. ^ current ifNil: [ current := self new ]
  182. ! !
  183. !TrappedSingleton class methodsFor: 'action'!
  184. start: args
  185. self current start: args
  186. ! !
  187. TrappedSingleton subclass: #Trapped
  188. instanceVariableNames: 'registry'
  189. package: 'Trapped-Frontend'!
  190. !Trapped methodsFor: 'accessing'!
  191. byName: aString
  192. ^ registry at: aString
  193. !
  194. register: aListKeyedEntity
  195. self register: aListKeyedEntity name: aListKeyedEntity class name
  196. !
  197. register: aListKeyedEntity name: aString
  198. registry at: aString put: aListKeyedEntity
  199. ! !
  200. !Trapped methodsFor: 'action'!
  201. descend: anArray snapshotDo: aBlock
  202. | tpsc |
  203. tpsc := TrappedPathStack current.
  204. tpsc append: anArray do: [
  205. | path model |
  206. path := tpsc elements copy.
  207. model := self byName: path first.
  208. aBlock value: (TrappedSnapshot new path: path model: model)
  209. ]
  210. !
  211. injectToJQuery: aJQuery
  212. aJQuery each: [ :index :elem |
  213. | jq |
  214. jq := elem asJQuery.
  215. (jq is: '[data-trap]')
  216. ifTrue: [
  217. | parsed |
  218. parsed := Trapped parse: (jq attr: 'data-trap').
  219. jq removeAttr: 'data-trap'.
  220. parsed do: [ :rule |
  221. (HTMLCanvas onJQuery: jq) root trap: rule first processors: (rule at: 2 ifAbsent: [#()]) ] ].
  222. self injectToJQuery: jq children ]
  223. !
  224. start: args
  225. args do: [ :each | self register: each ].
  226. self injectToJQuery: 'html' asJQuery
  227. ! !
  228. !Trapped methodsFor: 'initialization'!
  229. initialize
  230. super initialize.
  231. registry := #{}.
  232. ! !
  233. !Trapped class methodsFor: 'accessing'!
  234. parse: aString
  235. ^ (aString tokenize: '.') collect: [ :rule |
  236. (rule tokenize: ':') collect: [ :message |
  237. | result stack anArray |
  238. anArray := message tokenize: ' '.
  239. result := #().
  240. stack := { result }.
  241. anArray do: [ :each |
  242. | asNum inner close |
  243. close := 0.
  244. inner := each.
  245. [ inner notEmpty and: [ inner first = '(' ]] whileTrue: [ inner := inner allButFirst. stack add: (stack last add: #()) ].
  246. [ inner notEmpty and: [ inner last = ')' ]] whileTrue: [ inner := inner allButLast. close := close + 1 ].
  247. (inner notEmpty and: [ inner first = '#' ]) ifTrue: [ inner := { inner allButFirst } ].
  248. asNum := inner isString ifTrue: [ (inner ifEmpty: [ 'NaN' ]) asNumber ] ifFalse: [ inner ].
  249. asNum = asNum ifTrue: [ stack last add: asNum ] ifFalse: [
  250. inner ifNotEmpty: [ stack last add: inner ] ].
  251. close timesRepeat: [ stack removeLast ] ].
  252. result ] ]
  253. ! !
  254. !Trapped class methodsFor: 'private'!
  255. envelope: envelope loop: model before: endjq tag: aSymbol do: aBlock
  256. | envjq |
  257. envjq := envelope asJQuery.
  258. model withIndexDo: [ :item :i |
  259. envelope with: [ :html | (html perform: aSymbol) trap: {i} read: aBlock ].
  260. envjq children detach insertBefore: endjq.
  261. ].
  262. envjq remove
  263. !
  264. loop: model between: start and: end tag: aSymbol do: aBlock
  265. (start asJQuery nextUntil: end element) remove.
  266. start with: [ :html | model ifNotNil: [
  267. self envelope: html div loop: model before: end asJQuery tag: aSymbol do: aBlock
  268. ]]
  269. ! !
  270. TrappedSingleton subclass: #TrappedPathStack
  271. instanceVariableNames: 'elements'
  272. package: 'Trapped-Frontend'!
  273. !TrappedPathStack methodsFor: 'accessing'!
  274. elements
  275. ^elements
  276. ! !
  277. !TrappedPathStack methodsFor: 'descending'!
  278. append: anArray do: aBlock
  279. self with: elements, anArray do: aBlock
  280. !
  281. with: anArray do: aBlock
  282. | old |
  283. old := elements.
  284. [ elements := anArray.
  285. aBlock value ] ensure: [ elements := old ]
  286. ! !
  287. !TrappedPathStack methodsFor: 'initialization'!
  288. initialize
  289. super initialize.
  290. elements := #().
  291. ! !
  292. Object subclass: #TrappedSnapshot
  293. instanceVariableNames: 'path model'
  294. package: 'Trapped-Frontend'!
  295. !TrappedSnapshot methodsFor: 'accessing'!
  296. model
  297. ^model
  298. !
  299. path
  300. ^path
  301. !
  302. path: anArray model: aTrappedMW
  303. path := anArray.
  304. model := aTrappedMW
  305. ! !
  306. !TrappedSnapshot methodsFor: 'action'!
  307. do: aBlock
  308. TrappedPathStack current with: path do: [ aBlock value: model ]
  309. !
  310. modify: aBlock
  311. self model modify: self path allButFirst do: aBlock
  312. !
  313. watch: aBlock
  314. self model watch: self path allButFirst do: aBlock
  315. ! !
  316. !Array methodsFor: '*Trapped-Frontend'!
  317. trapDescend: aBlock
  318. Trapped current descend: self snapshotDo: aBlock
  319. ! !
  320. !HTMLCanvas methodsFor: '*Trapped-Frontend'!
  321. trapIter: path tag: aSymbol do: aBlock
  322. | start end |
  323. self with: [ :html | start := html script. end := html script ].
  324. start trap: path read: [ :model |
  325. Trapped loop: model between: start and: end tag: aSymbol do: aBlock.
  326. ]
  327. ! !
  328. !TagBrush methodsFor: '*Trapped-Frontend'!
  329. trap: path
  330. self trap: path processors: #()
  331. !
  332. trap: path processors: anArray
  333. path trapDescend: [ :snap |
  334. (TrappedProcessingChain newFromProcessorSpecs: anArray)
  335. forSnapshot: snap andBrush: self ]
  336. !
  337. trap: path read: aBlock
  338. path trapDescend: [ :snap |
  339. snap watch: [ :data |
  340. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  341. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  342. ]
  343. ]
  344. !
  345. trapGuard: anArray contents: aBlock
  346. #() trapDescend: [ :snap |
  347. | shown |
  348. shown := nil.
  349. self trap: anArray read: [ :gdata |
  350. | sanitized |
  351. sanitized := gdata ifNil: [ false ].
  352. shown = sanitized ifFalse: [
  353. shown := sanitized.
  354. shown
  355. ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
  356. ifFalse: [ self asJQuery hide; empty ] ] ] ]
  357. ! !