Trapped-Frontend.st 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  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: 'action'!
  22. modifyTarget
  23. self target modify: [ self value ]
  24. !
  25. modifyTargetByPerforming: aString
  26. self target modify: [ :m | m perform: aString ]
  27. !
  28. toTargetAttr: aString
  29. self target asJQuery attr: aString put: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
  30. !
  31. toTargetContents
  32. self target contents: self value
  33. !
  34. toTargetValue
  35. self target asJQuery val: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
  36. ! !
  37. !TrappedDataCarrier methodsFor: 'initialization'!
  38. initialize
  39. super initialize.
  40. model := true
  41. ! !
  42. !TrappedDataCarrier class methodsFor: 'not yet classified'!
  43. on: aProcessingChain target: anObject
  44. ^self new
  45. chain: aProcessingChain;
  46. target: anObject;
  47. yourself
  48. ! !
  49. TrappedDataCarrier subclass: #TrappedDataCarrierToModel
  50. instanceVariableNames: 'index'
  51. package: 'Trapped-Frontend'!
  52. !TrappedDataCarrierToModel methodsFor: 'not yet classified'!
  53. proceed
  54. index := index ifNil: [ chain lastProcessorNo ] ifNotNil: [ index - 1 ].
  55. (chain processorNo: index) toModel: self
  56. ! !
  57. TrappedDataCarrier subclass: #TrappedDataCarrierToView
  58. instanceVariableNames: 'index'
  59. package: 'Trapped-Frontend'!
  60. !TrappedDataCarrierToView methodsFor: 'not yet classified'!
  61. proceed
  62. index := index ifNil: [ chain firstProcessorNo ] ifNotNil: [ index + 1 ].
  63. (chain processorNo: index) toView: self
  64. ! !
  65. Object subclass: #TrappedProcessingChain
  66. instanceVariableNames: 'processors'
  67. package: 'Trapped-Frontend'!
  68. !TrappedProcessingChain methodsFor: 'accessing'!
  69. firstProcessorNo
  70. ^1
  71. !
  72. lastProcessorNo
  73. ^processors size
  74. !
  75. processorNo: aNumber
  76. ^processors at: aNumber
  77. !
  78. processors: anArray
  79. processors := anArray
  80. ! !
  81. !TrappedProcessingChain methodsFor: 'action'!
  82. forSnapshot: aSnapshot andBrush: aTagBrush
  83. | toViewCarrier toModelCarrier |
  84. toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
  85. toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
  86. processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].
  87. toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]
  88. ! !
  89. !TrappedProcessingChain class methodsFor: 'instance creation'!
  90. new: anArray
  91. (anArray detect: [ :each | each isExpectingModelData ] ifNone: [ nil ])
  92. ifNil: [ anArray add: self dataTerminator ]
  93. ifNotNil: [ anArray addFirst: self blackboardReaderWriter ].
  94. ^self new
  95. processors: anArray;
  96. yourself
  97. !
  98. newFromProcessorSpecs: anArray
  99. ^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each isString
  100. ifTrue: [ TrappedProcessor perform: each ]
  101. ifFalse: [
  102. | selector args |
  103. selector := ''.
  104. args := #().
  105. each withIndexDo: [ :element :index | index odd
  106. ifTrue: [ selector := selector, element ]
  107. ifFalse: [ selector := selector, ':'. args add: element ] ].
  108. TrappedProcessor perform: selector withArguments: args ] ])
  109. ! !
  110. !TrappedProcessingChain class methodsFor: 'private'!
  111. blackboardReaderWriter
  112. ^TrappedProcessorBlackboard new
  113. !
  114. dataTerminator
  115. ^TrappedProcessorTerminator new
  116. ! !
  117. Object subclass: #TrappedProcessor
  118. instanceVariableNames: ''
  119. package: 'Trapped-Frontend'!
  120. !TrappedProcessor commentStamp!
  121. I am a processing step in TrappedProcessingChain.
  122. I am stateless flyweight (aka servant)
  123. and will get all necessary data as arguments in API calls.
  124. My public API is:
  125. - installToView:toModel:
  126. This gets two TrappedDataCarriers set up without actual data
  127. and at the beginning of their chains. It should do one-time
  128. installation task needed (install event handlers etc.).
  129. To start a chain, do: dataCarrier copy value: data; proceed.
  130. - toView:
  131. This performs transformation of TrappedDataCarrier on its way from model to view.
  132. Should call aDataCarrier proceed to proceed to subsequent step.
  133. - toModel:
  134. This performs transformation of TrappedDataCarrier on its way from view to model.
  135. Should call aDataCarrier proceed to proceed to subsequent step.!
  136. !TrappedProcessor methodsFor: 'data transformation'!
  137. toModel: aDataCarrier
  138. "by default, proceed"
  139. aDataCarrier proceed
  140. !
  141. toView: aDataCarrier
  142. "by default, proceed"
  143. aDataCarrier proceed
  144. ! !
  145. !TrappedProcessor methodsFor: 'installation'!
  146. installToView: aDataCarrier toModel: anotherDataCarrier
  147. "by default, do nothing"
  148. ! !
  149. !TrappedProcessor methodsFor: 'testing'!
  150. isExpectingModelData
  151. ^false
  152. ! !
  153. !TrappedProcessor class methodsFor: 'factory'!
  154. contents
  155. ^TrappedProcessorContents new
  156. !
  157. guardProc: aString
  158. ^TrappedProcessorGuardProc new: aString
  159. !
  160. inputChecked
  161. ^TrappedProcessorInputChecked new
  162. !
  163. inputValue
  164. ^TrappedProcessorInputValue new
  165. !
  166. path
  167. ^TrappedProcessorDescend new
  168. !
  169. signal: aString
  170. ^TrappedProcessorSignal new: aString
  171. !
  172. whenClicked
  173. ^TrappedProcessorWhenClicked new
  174. !
  175. whenSubmitted
  176. ^TrappedProcessorWhenSubmitted new
  177. !
  178. widget: aString
  179. ^TrappedProcessorWidget new: aString
  180. ! !
  181. TrappedProcessor subclass: #TrappedDataExpectingProcessor
  182. instanceVariableNames: ''
  183. package: 'Trapped-Frontend'!
  184. !TrappedDataExpectingProcessor commentStamp!
  185. I answer true to isExpectingModelData and serve as a base class
  186. for processor that present / change model data.
  187. When at least one of my instances is present in the chain,
  188. automatic databinding processor is added at the beginning
  189. (the data-binding scenario); otherwise, the chain
  190. is run immediately with true as data (run-once scenario).!
  191. !TrappedDataExpectingProcessor methodsFor: 'testing'!
  192. isExpectingModelData
  193. ^true
  194. ! !
  195. TrappedDataExpectingProcessor subclass: #TrappedProcessorContents
  196. instanceVariableNames: ''
  197. package: 'Trapped-Frontend'!
  198. !TrappedProcessorContents commentStamp!
  199. I put data into target via contents: in toView:!
  200. !TrappedProcessorContents methodsFor: 'data transformation'!
  201. toView: aDataCarrier
  202. aDataCarrier toTargetContents
  203. ! !
  204. TrappedDataExpectingProcessor subclass: #TrappedProcessorInputChecked
  205. instanceVariableNames: ''
  206. package: 'Trapped-Frontend'!
  207. !TrappedProcessorInputChecked commentStamp!
  208. I bind to checkbox checked attribute.!
  209. !TrappedProcessorInputChecked methodsFor: 'data transformation'!
  210. toView: aDataCarrier
  211. aDataCarrier toTargetAttr: 'checked'
  212. ! !
  213. !TrappedProcessorInputChecked methodsFor: 'installation'!
  214. installToView: aDataCarrier toModel: anotherDataCarrier
  215. | brush |
  216. brush := aDataCarrier target.
  217. brush onChange: [ anotherDataCarrier copy value: (brush asJQuery attr: 'checked') notNil; proceed ]
  218. ! !
  219. TrappedDataExpectingProcessor subclass: #TrappedProcessorInputValue
  220. instanceVariableNames: ''
  221. package: 'Trapped-Frontend'!
  222. !TrappedProcessorInputValue commentStamp!
  223. I bind to input value.!
  224. !TrappedProcessorInputValue methodsFor: 'data transformation'!
  225. toView: aDataCarrier
  226. aDataCarrier toTargetValue
  227. ! !
  228. !TrappedProcessorInputValue methodsFor: 'installation'!
  229. installToView: aDataCarrier toModel: anotherDataCarrier
  230. | brush |
  231. brush := aDataCarrier target.
  232. brush onChange: [ anotherDataCarrier copy value: brush asJQuery val; proceed ]
  233. ! !
  234. TrappedProcessor subclass: #TrappedProcessorBlackboard
  235. instanceVariableNames: ''
  236. package: 'Trapped-Frontend'!
  237. !TrappedProcessorBlackboard commentStamp!
  238. I am used internally to fetch data from blackboard
  239. or write it back.
  240. I am added to the beginning of the chain
  241. when the chain contains at least one element
  242. that isExpectingModelData (see TrappedDataExpectingProcessor).!
  243. !TrappedProcessorBlackboard methodsFor: 'data transformation'!
  244. toModel: aDataCarrier
  245. aDataCarrier modifyTarget
  246. ! !
  247. !TrappedProcessorBlackboard methodsFor: 'installation'!
  248. installToView: aDataCarrier toModel: anotherDataCarrier
  249. | snap |
  250. snap := anotherDataCarrier target.
  251. snap watch: [ :data |
  252. (aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  253. snap do: [ aDataCarrier copy value: data; proceed ] ].
  254. aDataCarrier value: false
  255. ! !
  256. TrappedProcessor subclass: #TrappedProcessorDescend
  257. instanceVariableNames: ''
  258. package: 'Trapped-Frontend'!
  259. !TrappedProcessorDescend commentStamp!
  260. I intepret data-trap in descendants of my brush.!
  261. !TrappedProcessorDescend methodsFor: 'data transformation'!
  262. toView: aDataCarrier
  263. Trapped current injectToJQuery: aDataCarrier target asJQuery children
  264. ! !
  265. TrappedProcessor subclass: #TrappedProcessorGuardBase
  266. instanceVariableNames: 'guardPath'
  267. package: 'Trapped-Frontend'!
  268. !TrappedProcessorGuardBase commentStamp!
  269. I serve as base class for brush-guarding processors.
  270. I cover instantiation and subclasses have to provide
  271. implementation of toVIew: that react appropriately to guard releasing.!
  272. !TrappedProcessorGuardBase methodsFor: 'accessing'!
  273. guardPath: anArray
  274. guardPath := anArray
  275. ! !
  276. !TrappedProcessorGuardBase methodsFor: 'data transformation'!
  277. toModel: aDataCarrier
  278. "stop"
  279. !
  280. toView: aDataCarrier
  281. self subclassResponsibility
  282. ! !
  283. !TrappedProcessorGuardBase class methodsFor: 'instance creation'!
  284. new: anArray
  285. ^ self new
  286. guardPath: anArray;
  287. yourself
  288. ! !
  289. TrappedProcessorGuardBase subclass: #TrappedProcessorGuardProc
  290. instanceVariableNames: ''
  291. package: 'Trapped-Frontend'!
  292. !TrappedProcessorGuardProc commentStamp!
  293. I am used to guard contents filling process of the brush I am installed on.
  294. I observe guard expression in the model,
  295. and when it changes to nil or false, I delete the brush contents;
  296. on the other hand, when it changes to non-nil and non-false,
  297. I run the rest on the chain, which should be one-time
  298. that sets up the contents,!
  299. !TrappedProcessorGuardProc methodsFor: 'data transformation'!
  300. toView: aDataCarrier
  301. | frozen |
  302. frozen := aDataCarrier copy.
  303. frozen target trapGuard: guardPath contents: [ frozen copy proceed ]
  304. ! !
  305. TrappedProcessor subclass: #TrappedProcessorSignal
  306. instanceVariableNames: 'selector'
  307. package: 'Trapped-Frontend'!
  308. !TrappedProcessorSignal commentStamp!
  309. Instead of writing data directly to model,
  310. I instead modify it by sending a message specified when instantiating me.!
  311. !TrappedProcessorSignal methodsFor: 'accessing'!
  312. selector: aString
  313. selector := aString
  314. ! !
  315. !TrappedProcessorSignal methodsFor: 'data transformation'!
  316. toModel: aDataCarrier
  317. aDataCarrier modifyTargetByPerforming: selector
  318. !
  319. toView: aDataCarrier
  320. "stop"
  321. ! !
  322. !TrappedProcessorSignal class methodsFor: 'instance creation'!
  323. new: aString
  324. ^self new
  325. selector: aString;
  326. yourself
  327. ! !
  328. TrappedProcessor subclass: #TrappedProcessorTerminator
  329. instanceVariableNames: ''
  330. package: 'Trapped-Frontend'!
  331. !TrappedProcessorTerminator commentStamp!
  332. I do not proceed in toView:.
  333. I am added automatically to end of chain when it does not contain
  334. any element that isExpectingModelData (see TrappedDataExpectingProcessor).!
  335. !TrappedProcessorTerminator methodsFor: 'data transformation'!
  336. toView: aDataCarrier
  337. "stop"
  338. ! !
  339. TrappedProcessor subclass: #TrappedProcessorWhenClicked
  340. instanceVariableNames: ''
  341. package: 'Trapped-Frontend'!
  342. !TrappedProcessorWhenClicked commentStamp!
  343. I bind to an element and send true to blackboard when clicked.!
  344. !TrappedProcessorWhenClicked methodsFor: 'installation'!
  345. installToView: aDataCarrier toModel: anotherDataCarrier
  346. aDataCarrier target onClick: [ anotherDataCarrier copy proceed. false ]
  347. ! !
  348. TrappedProcessor subclass: #TrappedProcessorWhenSubmitted
  349. instanceVariableNames: ''
  350. package: 'Trapped-Frontend'!
  351. !TrappedProcessorWhenSubmitted commentStamp!
  352. I bind to a form and send true to blackboard when submitted.!
  353. !TrappedProcessorWhenSubmitted methodsFor: 'installation'!
  354. installToView: aDataCarrier toModel: anotherDataCarrier
  355. aDataCarrier target onSubmit: [ anotherDataCarrier copy proceed. false ]
  356. ! !
  357. TrappedProcessor subclass: #TrappedProcessorWidget
  358. instanceVariableNames: 'viewName'
  359. package: 'Trapped-Frontend'!
  360. !TrappedProcessorWidget commentStamp!
  361. I insert a widget instance of the class specified when creating me.!
  362. !TrappedProcessorWidget methodsFor: 'accessing'!
  363. viewName: aString
  364. viewName := aString
  365. ! !
  366. !TrappedProcessorWidget methodsFor: 'data transformation'!
  367. toView: aDataCarrier
  368. aDataCarrier target with: (Smalltalk current at: viewName) new
  369. ! !
  370. !TrappedProcessorWidget class methodsFor: 'instance creation'!
  371. new: aString
  372. ^self new
  373. viewName: aString;
  374. yourself
  375. ! !
  376. Object subclass: #TrappedSingleton
  377. instanceVariableNames: ''
  378. package: 'Trapped-Frontend'!
  379. !TrappedSingleton methodsFor: 'action'!
  380. start: args
  381. ^ self subclassResponsibility
  382. ! !
  383. TrappedSingleton class instanceVariableNames: 'current'!
  384. !TrappedSingleton class methodsFor: 'accessing'!
  385. current
  386. ^ current ifNil: [ current := self new ]
  387. ! !
  388. !TrappedSingleton class methodsFor: 'action'!
  389. start: args
  390. self current start: args
  391. ! !
  392. TrappedSingleton subclass: #Trapped
  393. instanceVariableNames: 'registry'
  394. package: 'Trapped-Frontend'!
  395. !Trapped methodsFor: 'accessing'!
  396. byName: aString
  397. ^ registry at: aString
  398. !
  399. register: aListKeyedEntity
  400. self register: aListKeyedEntity name: aListKeyedEntity class name
  401. !
  402. register: aListKeyedEntity name: aString
  403. registry at: aString put: aListKeyedEntity
  404. ! !
  405. !Trapped methodsFor: 'action'!
  406. descend: anArray snapshotDo: aBlock
  407. | tpsc |
  408. tpsc := TrappedPathStack current.
  409. tpsc append: anArray do: [
  410. | path model |
  411. path := tpsc elements copy.
  412. model := self byName: path first.
  413. aBlock value: (TrappedSnapshot new path: path model: model)
  414. ]
  415. !
  416. injectToJQuery: aJQuery
  417. aJQuery each: [ :index :elem |
  418. | jq |
  419. jq := elem asJQuery.
  420. (jq is: '[data-trap]')
  421. ifTrue: [
  422. | parsed |
  423. parsed := Trapped parse: (jq attr: 'data-trap').
  424. jq removeAttr: 'data-trap'.
  425. parsed do: [ :rule |
  426. (HTMLCanvas onJQuery: jq) root trap: rule first processors: (rule at: 2 ifAbsent: [#()]) ] ]
  427. ifFalse: [ self injectToJQuery: jq children ] ]
  428. !
  429. start: args
  430. args do: [ :each | self register: each ].
  431. self injectToJQuery: 'html' asJQuery
  432. ! !
  433. !Trapped methodsFor: 'initialization'!
  434. initialize
  435. super initialize.
  436. registry := #{}.
  437. ! !
  438. !Trapped class methodsFor: 'accessing'!
  439. parse: aString
  440. ^ (aString tokenize: '.') collect: [ :rule |
  441. (rule tokenize: ':') collect: [ :message |
  442. | result stack anArray |
  443. anArray := message tokenize: ' '.
  444. result := #().
  445. stack := { result }.
  446. anArray do: [ :each |
  447. | asNum inner close |
  448. close := 0.
  449. inner := each.
  450. [ inner notEmpty and: [ inner first = '(' ]] whileTrue: [ inner := inner allButFirst. stack add: (stack last add: #()) ].
  451. [ inner notEmpty and: [ inner last = ')' ]] whileTrue: [ inner := inner allButLast. close := close + 1 ].
  452. (inner notEmpty and: [ inner first = '#' ]) ifTrue: [ inner := { inner allButFirst } ].
  453. asNum := inner isString ifTrue: [ (inner ifEmpty: [ 'NaN' ]) asNumber ] ifFalse: [ inner ].
  454. asNum = asNum ifTrue: [ stack last add: asNum ] ifFalse: [
  455. inner ifNotEmpty: [ stack last add: inner ] ].
  456. close timesRepeat: [ stack removeLast ] ].
  457. result ] ]
  458. ! !
  459. !Trapped class methodsFor: 'private'!
  460. envelope: envelope loop: model before: endjq tag: aSymbol do: aBlock
  461. | envjq |
  462. envjq := envelope asJQuery.
  463. model withIndexDo: [ :item :i |
  464. envelope with: [ :html | (html perform: aSymbol) trap: {i} read: aBlock ].
  465. envjq children detach insertBefore: endjq.
  466. ].
  467. envjq remove
  468. !
  469. loop: model between: start and: end tag: aSymbol do: aBlock
  470. (start asJQuery nextUntil: end element) remove.
  471. start with: [ :html | model ifNotNil: [
  472. self envelope: html div loop: model before: end asJQuery tag: aSymbol do: aBlock
  473. ]]
  474. ! !
  475. TrappedSingleton subclass: #TrappedPathStack
  476. instanceVariableNames: 'elements'
  477. package: 'Trapped-Frontend'!
  478. !TrappedPathStack methodsFor: 'accessing'!
  479. elements
  480. ^elements
  481. ! !
  482. !TrappedPathStack methodsFor: 'descending'!
  483. append: anArray do: aBlock
  484. self with: elements, anArray do: aBlock
  485. !
  486. with: anArray do: aBlock
  487. | old |
  488. old := elements.
  489. [ elements := anArray.
  490. aBlock value ] ensure: [ elements := old ]
  491. ! !
  492. !TrappedPathStack methodsFor: 'initialization'!
  493. initialize
  494. super initialize.
  495. elements := #().
  496. ! !
  497. Object subclass: #TrappedSnapshot
  498. instanceVariableNames: 'path model'
  499. package: 'Trapped-Frontend'!
  500. !TrappedSnapshot methodsFor: 'accessing'!
  501. model
  502. ^model
  503. !
  504. path
  505. ^path
  506. !
  507. path: anArray model: aTrappedMW
  508. path := anArray.
  509. model := aTrappedMW
  510. ! !
  511. !TrappedSnapshot methodsFor: 'action'!
  512. do: aBlock
  513. TrappedPathStack current with: path do: [ aBlock value: model ]
  514. !
  515. modify: aBlock
  516. self model modify: self path allButFirst do: aBlock
  517. !
  518. watch: aBlock
  519. self model watch: self path allButFirst do: aBlock
  520. ! !
  521. !Array methodsFor: '*Trapped-Frontend'!
  522. trapDescend: aBlock
  523. Trapped current descend: self snapshotDo: aBlock
  524. ! !
  525. !HTMLCanvas methodsFor: '*Trapped-Frontend'!
  526. trapIter: path tag: aSymbol do: aBlock
  527. | start end |
  528. self with: [ :html | start := html script. end := html script ].
  529. start trap: path read: [ :model |
  530. Trapped loop: model between: start and: end tag: aSymbol do: aBlock.
  531. ]
  532. ! !
  533. !TagBrush methodsFor: '*Trapped-Frontend'!
  534. trap: path
  535. self trap: path processors: #()
  536. !
  537. trap: path processors: anArray
  538. path trapDescend: [ :snap |
  539. (TrappedProcessingChain newFromProcessorSpecs: anArray)
  540. forSnapshot: snap andBrush: self ]
  541. !
  542. trap: path read: aBlock
  543. path trapDescend: [ :snap |
  544. snap watch: [ :data |
  545. (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
  546. snap do: [ self with: [ :html | aBlock value: data value: html ] ]
  547. ]
  548. ]
  549. !
  550. trapGuard: anArray contents: aBlock
  551. #() trapDescend: [ :snap |
  552. | shown |
  553. shown := nil.
  554. self trap: anArray read: [ :gdata |
  555. | sanitized |
  556. sanitized := gdata ifNil: [ false ].
  557. shown = sanitized ifFalse: [
  558. shown := sanitized.
  559. shown
  560. ifTrue: [ snap do: [ self contents: aBlock ]. self asJQuery show ]
  561. ifFalse: [ self asJQuery hide; empty ] ] ] ]
  562. ! !