Trapped-Frontend.st 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. Smalltalk current createPackage: 'Trapped-Frontend' properties: #{}!
  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. KeyedPubSubBase subclass: #TrappedDispatcher
  51. instanceVariableNames: ''
  52. package: 'Trapped-Frontend'!
  53. !TrappedDispatcher commentStamp!
  54. I am base class for change event dispatchers.
  55. I manage changed path - action block subscriptions.
  56. These subscription are instances of TrappedSubscription
  57. My subclasses need to provide implementation for:
  58. add:
  59. do:
  60. clean
  61. (optionally) run!
  62. !TrappedDispatcher methodsFor: 'action'!
  63. subscriptionKey: key block: aBlock
  64. ^TrappedSubscription new key: key block: aBlock; yourself
  65. ! !
  66. Widget subclass: #TrappedDumbView
  67. instanceVariableNames: ''
  68. package: 'Trapped-Frontend'!
  69. !TrappedDumbView commentStamp!
  70. I just read and show an actual path.!
  71. !TrappedDumbView methodsFor: 'rendering'!
  72. renderOn: html
  73. html root trap: #()
  74. ! !
  75. Object subclass: #TrappedModelWrapper
  76. instanceVariableNames: 'dispatcher payload'
  77. package: 'Trapped-Frontend'!
  78. !TrappedModelWrapper commentStamp!
  79. I am base class for model wrappers.
  80. I wrap a model which can be any object.
  81. My subclasses need to provide implementation for:
  82. read:do:
  83. modify:do:
  84. (optionally) name
  85. and must issue these call when initializing:
  86. model:
  87. dispatcher: (with a subclass of TrappedDispatcher)!
  88. !TrappedModelWrapper methodsFor: 'accessing'!
  89. dispatcher
  90. ^dispatcher
  91. !
  92. dispatcher: aDispatcher
  93. dispatcher := aDispatcher
  94. !
  95. model: anObject
  96. payload := anObject.
  97. self dispatcher changed: #()
  98. !
  99. name
  100. ^ self class name
  101. ! !
  102. !TrappedModelWrapper methodsFor: 'action'!
  103. start
  104. Trapped current register: self name: self name
  105. !
  106. watch: path do: aBlock
  107. self dispatcher on: path hook: [ self read: path do: aBlock ]
  108. ! !
  109. !TrappedModelWrapper class methodsFor: 'action'!
  110. start
  111. ^self new start; yourself
  112. ! !
  113. TrappedModelWrapper subclass: #TrappedMWDirect
  114. instanceVariableNames: ''
  115. package: 'Trapped-Frontend'!
  116. !TrappedMWDirect commentStamp!
  117. I am TrappedModelWrapper that directly manipulate
  118. the object passed to model:!
  119. !TrappedMWDirect methodsFor: 'action'!
  120. modify: path do: aBlock
  121. | newValue eavModel |
  122. eavModel := path asEavModel.
  123. newValue := aBlock value: (eavModel on: payload).
  124. [ eavModel on: payload put: newValue ] ensure: [ self dispatcher changed: path ]
  125. !
  126. read: path do: aBlock
  127. | eavModel |
  128. eavModel := path asEavModel.
  129. aBlock value: (eavModel on: payload)
  130. ! !
  131. TrappedModelWrapper subclass: #TrappedMWIsolated
  132. instanceVariableNames: ''
  133. package: 'Trapped-Frontend'!
  134. !TrappedMWIsolated commentStamp!
  135. I am TrappedModelWrapper than wrap access
  136. to an object passed to model: via Isolator.!
  137. !TrappedMWIsolated methodsFor: 'accessing'!
  138. model: anObject
  139. super model: (Isolator on: anObject)
  140. ! !
  141. !TrappedMWIsolated methodsFor: 'action'!
  142. modify: path do: aBlock
  143. | eavModel |
  144. eavModel := ({#root},path) asEavModel.
  145. [ payload model: eavModel modify: aBlock ] ensure: [ self dispatcher changed: path ]
  146. !
  147. read: path do: aBlock
  148. | eavModel |
  149. eavModel := ({#root},path) asEavModel.
  150. payload model: eavModel read: aBlock
  151. ! !
  152. Object subclass: #TrappedSingleton
  153. instanceVariableNames: ''
  154. package: 'Trapped-Frontend'!
  155. !TrappedSingleton methodsFor: 'action'!
  156. start
  157. ^ self subclassResponsibility
  158. ! !
  159. TrappedSingleton class instanceVariableNames: 'current'!
  160. !TrappedSingleton class methodsFor: 'accessing'!
  161. current
  162. ^ current ifNil: [ current := self new ]
  163. ! !
  164. !TrappedSingleton class methodsFor: 'action'!
  165. start
  166. self current start
  167. ! !
  168. TrappedSingleton subclass: #Trapped
  169. instanceVariableNames: 'registry'
  170. package: 'Trapped-Frontend'!
  171. !Trapped methodsFor: 'accessing'!
  172. byName: aString
  173. ^ registry at: aString
  174. !
  175. register: aFly name: aString
  176. registry at: aString put: aFly
  177. ! !
  178. !Trapped methodsFor: 'action'!
  179. descend: anArray snapshotDo: aBlock
  180. | tpsc |
  181. tpsc := TrappedPathStack current.
  182. tpsc append: anArray do: [
  183. | path model |
  184. path := tpsc elements copy.
  185. model := self byName: path first.
  186. aBlock value: (TrappedSnapshot new path: path model: model)
  187. ]
  188. !
  189. start
  190. '[data-trap]' asJQuery each: [ :index :elem |
  191. | trap jq viewName modelName tokens path |
  192. jq := elem asJQuery.
  193. trap := jq attr: 'data-trap'.
  194. tokens := trap tokenize: ':'.
  195. tokens size = 1 ifTrue: [ tokens := { 'TrappedDumbView' }, tokens ].
  196. viewName := tokens first.
  197. tokens := (tokens second tokenize: ' ') select: [ :each | each notEmpty ].
  198. modelName := tokens first.
  199. path := Trapped parse: tokens allButFirst.
  200. { modelName }, path trapDescend: [(Smalltalk current at: viewName) new appendToJQuery: jq].
  201. ]
  202. ! !
  203. !Trapped methodsFor: 'binders'!
  204. binder: aTagBrush
  205. "Prototype; will select based on tag etc."
  206. | binder tag |
  207. tag := aTagBrush element nodeName.
  208. tag = 'INPUT' ifTrue: [
  209. | type |
  210. type := aTagBrush asJQuery attr: 'type'.
  211. type = 'checkbox' ifTrue: [ binder := TrappedCheckedBinder new ].
  212. type = 'text' ifTrue: [ binder := TrappedValBinder new ]
  213. ].
  214. binder ifNil: [ binder := TrappedBinder new ].
  215. ^ binder brush: aTagBrush; yourself
  216. ! !
  217. !Trapped methodsFor: 'initialization'!
  218. initialize
  219. super initialize.
  220. registry := #{}.
  221. ! !
  222. !Trapped class methodsFor: 'accessing'!
  223. parse: anArray
  224. ^anArray collect: [ :each |
  225. | asNum |
  226. <asNum = parseInt(each)>.
  227. asNum = asNum ifTrue: [ asNum ] ifFalse: [
  228. each first = '#' ifTrue: [ each allButFirst asSymbol ] ifFalse: [ each ]]]
  229. ! !
  230. TrappedSingleton subclass: #TrappedPathStack
  231. instanceVariableNames: 'elements'
  232. package: 'Trapped-Frontend'!
  233. !TrappedPathStack methodsFor: 'accessing'!
  234. elements
  235. ^elements
  236. ! !
  237. !TrappedPathStack methodsFor: 'descending'!
  238. append: anArray do: aBlock
  239. self with: elements, anArray do: aBlock
  240. !
  241. with: anArray do: aBlock
  242. | old |
  243. old := elements.
  244. [ elements := anArray.
  245. aBlock value ] ensure: [ elements := old ]
  246. ! !
  247. !TrappedPathStack methodsFor: 'initialization'!
  248. initialize
  249. super initialize.
  250. elements := #().
  251. ! !
  252. Object subclass: #TrappedSnapshot
  253. instanceVariableNames: 'path model'
  254. package: 'Trapped-Frontend'!
  255. !TrappedSnapshot methodsFor: 'accessing'!
  256. model
  257. ^model
  258. !
  259. path
  260. ^path
  261. !
  262. path: anArray model: aTrappedMW
  263. path := anArray.
  264. model := aTrappedMW
  265. ! !
  266. !TrappedSnapshot methodsFor: 'action'!
  267. do: aBlock
  268. TrappedPathStack current with: path do: [ aBlock value: model ]
  269. !
  270. modify: aBlock
  271. self model modify: self path allButFirst do: aBlock
  272. ! !
  273. KeyedSubscriptionBase subclass: #TrappedSubscription
  274. instanceVariableNames: ''
  275. package: 'Trapped-Frontend'!
  276. !TrappedSubscription methodsFor: 'testing'!
  277. accepts: aKey
  278. ^aKey size <= key size and: [aKey = (key copyFrom: 1 to: aKey size)]
  279. ! !
  280. !Array methodsFor: '*Trapped-Frontend'!
  281. trapDescend: aBlock
  282. Trapped current descend: self snapshotDo: aBlock
  283. ! !
  284. !Array methodsFor: '*Trapped-Frontend'!
  285. trapDescend: aBlock
  286. Trapped current descend: self snapshotDo: aBlock
  287. ! !
  288. !TagBrush methodsFor: '*Trapped-Frontend'!
  289. trap: path
  290. (Trapped current binder: self) installFor: path
  291. !
  292. trap: path read: aBlock
  293. path trapDescend: [ :snap |
  294. snap model watch: snap path allButFirst do: [ :data |
  295. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  296. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  297. ]
  298. ]
  299. !
  300. trap: path toggle: aBlock
  301. self trap: path toggle: aBlock ifNotPresent: [ self asJQuery hide ]
  302. !
  303. trap: path toggle: aBlock ifNotPresent: anotherBlock
  304. | shown |
  305. shown := nil.
  306. self trap: path read: [ :data : html |
  307. shown = data notNil ifFalse: [
  308. shown := data notNil.
  309. self asJQuery empty; show.
  310. (shown ifTrue: [aBlock] ifFalse: [anotherBlock]) value: data value: html.
  311. ]
  312. ]
  313. !
  314. trapIter: path tag: aSymbol do: aBlock
  315. self trap: path read: [ :model :html |
  316. html root empty.
  317. model ifNotNil: [ model withIndexDo: [ :item :i |
  318. (html perform: aSymbol) trap: {i} read: aBlock
  319. ]]
  320. ]
  321. ! !