Trapped-Frontend.st 8.6 KB

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