Trapped-Frontend.st 8.6 KB

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