Moka-Core.st 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. Smalltalk createPackage: 'Moka-Core'!
  2. Object subclass: #MKController
  3. instanceVariableNames: 'view model'
  4. package: 'Moka-Core'!
  5. !MKController commentStamp!
  6. I implement the Controller part of the MVC pattern in Moka.
  7. I hold onto my `model` and `view`, set with `MKView >> controller:`.!
  8. !MKController methodsFor: 'accessing'!
  9. model
  10. ^ model
  11. !
  12. model: aModel
  13. model := aModel
  14. !
  15. view
  16. ^ view
  17. !
  18. view: aView
  19. view := aView
  20. ! !
  21. !MKController methodsFor: 'actions'!
  22. onChange: anEvent
  23. !
  24. onClick: anEvent
  25. !
  26. onDblClick: anEvent
  27. !
  28. onKeyDown: anEvent
  29. !
  30. onKeyPress: anEvent
  31. !
  32. onKeyUp: anEvent
  33. !
  34. onMouseEnter: anEvent
  35. !
  36. onMouseLeave: anEvent
  37. !
  38. onMouseMove: anEvent
  39. !
  40. onMouseOut: anEvent
  41. !
  42. onMouseOver: anEvent
  43. ! !
  44. MKController subclass: #MKAspectsController
  45. instanceVariableNames: ''
  46. package: 'Moka-Core'!
  47. !MKAspectsController commentStamp!
  48. I am an abstract controller for performing one action using an `aspect` on a model.
  49. ## API
  50. - Use `#aspect:` to plug a selector to be performed on the model
  51. - Subclasses can either use `#performActionWith:` or `#performAction` to evaluate the `aspect` selector on the model with one or no argument.!
  52. !MKAspectsController methodsFor: 'actions'!
  53. performAspectAction: aSelector
  54. self model perform: aSelector
  55. !
  56. performAspectAction: aSelector with: anObject
  57. self model
  58. perform: aSelector asMutator
  59. withArguments: { anObject }
  60. ! !
  61. MKAspectsController subclass: #MKSingleAspectController
  62. instanceVariableNames: ''
  63. package: 'Moka-Core'!
  64. !MKSingleAspectController commentStamp!
  65. I am an abstract controller used with single aspect views.
  66. My view must hold onto one aspect accessed with `#aspect`.!
  67. !MKSingleAspectController methodsFor: 'actions'!
  68. performAspectAction
  69. ^ self performAspectAction: self view aspect
  70. !
  71. performAspectActionWith: anObject
  72. ^ self
  73. performAspectAction: self view aspect
  74. with: anObject
  75. ! !
  76. Object subclass: #MKObservable
  77. instanceVariableNames: 'announcer'
  78. package: 'Moka-Core'!
  79. !MKObservable commentStamp!
  80. View models are typically subclasses of me.
  81. I implement the Observable part of the Observer pattern in Moka.
  82. The observer pattern is implemented through an `announcer` object.
  83. ## API
  84. - Listening
  85. Use `#on:do:` or `#on:send:to:` to listen to receiver changes
  86. - Triggering
  87. `#changed:` is the builtin method used to trigger `#update:` in views.
  88. Use `#announce:` in subclasses to trigger announcements to listeners.!
  89. !MKObservable methodsFor: 'announcements'!
  90. announce: anAnnouncement
  91. announcer announce: anAnnouncement
  92. !
  93. changed: aSelector
  94. "Trigger `#update:` to all listening aspect views"
  95. self announce: (MKAspectChanged aspect: aSelector)
  96. !
  97. on: anAnnouncement do: aBlock
  98. announcer on: anAnnouncement do: aBlock
  99. !
  100. on: anAnnouncement send: aSelector to: anObject
  101. announcer on: anAnnouncement send: aSelector to: anObject
  102. ! !
  103. !MKObservable methodsFor: 'initialization'!
  104. initialize
  105. super initialize.
  106. announcer := Announcer new
  107. ! !
  108. MKObservable subclass: #MKView
  109. instanceVariableNames: 'controller model root extraCssClass'
  110. package: 'Moka-Core'!
  111. !MKView commentStamp!
  112. I implement the View part of the MVC pattern in Moka.
  113. ## API
  114. - Instance can be created with the `MKView class >> model:*` convenience methods
  115. - rendering is done through `#renderContentOn:`, to be overridden in concrete view classes
  116. - `#update` provide updating facility, refreshing the entire view
  117. - subclasses can override `#defaultControllerClass` to provide a default controller specific to a view
  118. - subclasses can override `#observeModel`
  119. - Extra css classes can be added with `#extraCssClass:`.!
  120. !MKView methodsFor: 'accessing'!
  121. children
  122. "Answer all the sub-views of the receiver"
  123. ^ #()
  124. !
  125. controller
  126. "Answer the current receiver's controller.
  127. If no controller is installed yet, install the `defaultController`
  128. of the receiver and answer it."
  129. controller ifNil: [
  130. self controller: self defaultController ].
  131. ^ controller
  132. !
  133. controller: aController
  134. "Install `aController` to be the receiver's controller"
  135. controller := aController.
  136. aController
  137. view: self;
  138. model: self model
  139. !
  140. cssClass
  141. ^ String streamContents: [ :stream |
  142. stream << 'moka_view'.
  143. self extraCssClass ifNotEmpty: [
  144. stream << ' ' << self extraCssClass ] ]
  145. !
  146. cssStyle
  147. ^ ''
  148. !
  149. extraCssClass
  150. ^ extraCssClass ifNil: [ '' ]
  151. !
  152. extraCssClass: aString
  153. extraCssClass := aString
  154. !
  155. model
  156. ^ model
  157. !
  158. model: aModel
  159. model := aModel.
  160. self observeModel
  161. !
  162. tag
  163. ^ 'div'
  164. ! !
  165. !MKView methodsFor: 'actions'!
  166. blur
  167. root ifNotNil: [ root asJQuery blur ]
  168. !
  169. focus
  170. root ifNotNil: [ root asJQuery focus ]
  171. !
  172. remove
  173. "Removes the receiver from the DOM"
  174. root ifNotNil: [ root asJQuery remove ].
  175. self announce: (MKViewRemoved view: self)
  176. !
  177. resized
  178. "Action triggered when the view has been resized from the outside"
  179. self children do: [ :each | each resized ]
  180. ! !
  181. !MKView methodsFor: 'adding'!
  182. appendToBrush: aTagBrush
  183. self appendToJQuery: aTagBrush asJQuery
  184. !
  185. appendToJQuery: aJQuery
  186. self renderOn: (HTMLCanvas onJQuery: aJQuery)
  187. ! !
  188. !MKView methodsFor: 'converting'!
  189. asJQuery
  190. ^ root asJQuery
  191. ! !
  192. !MKView methodsFor: 'defaults'!
  193. defaultControllerClass
  194. ^ MKController
  195. ! !
  196. !MKView methodsFor: 'dom'!
  197. domPosition
  198. "Answer the position of the reciever in the page"
  199. | offset |
  200. offset := self asJQuery offset.
  201. ^ offset left @ offset top
  202. !
  203. domSize
  204. ^ self asJQuery width @ self asJQuery height
  205. ! !
  206. !MKView methodsFor: 'factory'!
  207. defaultController
  208. ^ self defaultControllerClass new
  209. ! !
  210. !MKView methodsFor: 'observing'!
  211. observeModel
  212. "No op. Override in subclasses"
  213. ! !
  214. !MKView methodsFor: 'private'!
  215. setupEventHandlers
  216. root
  217. onClick: [ :event | self controller onClick: event ];
  218. onDblClick: [ :event | self controller onDblClick: event ];
  219. onMouseEnter: [ :event | self controller onMouseEnter: event ];
  220. onMouseLeave: [ :event | self controller onMouseLeave: event ];
  221. onMouseOver: [ :event | self controller onMouseOver: event ];
  222. onMouseOut: [ :event | self controller onMouseOut: event ];
  223. onMouseMove: [ :event | self controller onMouseMove: event ];
  224. onKeyDown: [ :event | self controller onKeyDown: event ];
  225. onKeyUp: [ :event | self controller onKeyUp: event ];
  226. onKeyPress: [ :event | self controller onKeyPress: event ];
  227. onChange: [ :event | self controller onChange: event ]
  228. ! !
  229. !MKView methodsFor: 'rendering'!
  230. render
  231. "Append the receiver to the BODY element"
  232. self appendToJQuery: 'body' asJQuery
  233. !
  234. renderContentOn: html
  235. "Main rendering method, override in subclasses."
  236. !
  237. renderOn: html
  238. "Basic rendering method.
  239. Do not override this method, but `#renderContentOn:`"
  240. root := (html tag: self tag)
  241. class: self cssClass;
  242. style: self cssStyle;
  243. yourself.
  244. root with: [ self renderContentOn: html ].
  245. self setupEventHandlers
  246. ! !
  247. !MKView methodsFor: 'updating'!
  248. update
  249. "Update the view's content. Override in subclasses to fine-tune updating"
  250. root ifNil: [ ^ self ].
  251. root asJQuery empty.
  252. [ :html | self renderContentOn: html ]
  253. appendToJQuery: root asJQuery
  254. ! !
  255. !MKView class methodsFor: 'instance creation'!
  256. model: aModel
  257. ^ self new
  258. model: aModel;
  259. yourself
  260. !
  261. model: aModel controller: aController
  262. ^ (self model: aModel)
  263. controller: aController;
  264. yourself
  265. ! !
  266. MKView subclass: #MKLayoutView
  267. instanceVariableNames: 'layout'
  268. package: 'Moka-Core'!
  269. !MKLayoutView commentStamp!
  270. I implement the View part of the MVC pattern in Moka.
  271. ## API
  272. - Instance can be created with the `MKView class >> model:*` convenience methods
  273. - rendering is done through `#renderContentOn:`, to be overridden in concrete view classes
  274. - `#update` provide updating facility, refreshing the entire view
  275. - subclasses can override `#defaultControllerClass` to provide a default controller specific to a view
  276. - subclasses can override `#observeModel`
  277. - Extra css classes can be added with `#extraCssClass:`.!
  278. !MKLayoutView methodsFor: 'accessing'!
  279. cssStyle
  280. ^ self layout asCssString
  281. !
  282. layout
  283. ^ layout ifNil: [ layout := self defaultLayout ]
  284. ! !
  285. !MKLayoutView methodsFor: 'defaults'!
  286. defaultLayout
  287. ^ MKLayout new
  288. left: 0;
  289. top: 0;
  290. right: 0;
  291. bottom: 0;
  292. yourself
  293. ! !
  294. !MKLayoutView methodsFor: 'layout'!
  295. bottom
  296. ^ self layout bottom
  297. !
  298. bottom: aNumber
  299. self layout bottom: aNumber
  300. !
  301. centerX
  302. ^ self layout centerX
  303. !
  304. centerX: aNumber
  305. self layout centerX: aNumber
  306. !
  307. centerY
  308. ^ self layout centerY
  309. !
  310. centerY: aNumber
  311. self layout centerY: aNumber
  312. !
  313. height
  314. ^ self layout height
  315. !
  316. height: aNumber
  317. self layout height: aNumber
  318. !
  319. left
  320. ^ self layout left
  321. !
  322. left: aNumber
  323. self layout left: aNumber
  324. !
  325. right
  326. ^ self layout right
  327. !
  328. right: aNumber
  329. self layout right: aNumber
  330. !
  331. top
  332. ^ self layout top
  333. !
  334. top: aNumber
  335. self layout top: aNumber
  336. !
  337. width
  338. ^ self layout width
  339. !
  340. width: aNumber
  341. self layout width: aNumber
  342. ! !
  343. !MKLayoutView class methodsFor: 'instance creation'!
  344. model: aModel
  345. ^ self new
  346. model: aModel;
  347. yourself
  348. !
  349. model: aModel controller: aController
  350. ^ (self model: aModel)
  351. controller: aController;
  352. yourself
  353. ! !
  354. MKLayoutView subclass: #MKAspectsView
  355. instanceVariableNames: ''
  356. package: 'Moka-Core'!
  357. !MKAspectsView commentStamp!
  358. I am an abstract view which state depend on aspects of a model.!
  359. !MKAspectsView methodsFor: 'accessing'!
  360. valueForAspect: aSelector
  361. ^ self model perform: aSelector
  362. ! !
  363. !MKAspectsView methodsFor: 'defaults'!
  364. defaultControllerClass
  365. ^ MKAspectController
  366. ! !
  367. !MKAspectsView methodsFor: 'observing'!
  368. observeModel
  369. super observeModel.
  370. self model
  371. on: MKAspectChanged
  372. send: 'update:'
  373. to: self
  374. ! !
  375. !MKAspectsView methodsFor: 'updating'!
  376. update: anAnnouncement
  377. "Override in subclasses to match the view's aspect(s)"
  378. ! !
  379. MKAspectsView subclass: #MKSingleAspectView
  380. instanceVariableNames: 'aspect'
  381. package: 'Moka-Core'!
  382. !MKSingleAspectView commentStamp!
  383. I am an abstract view which state depend on an `aspect` of a model.
  384. ##API
  385. - Use the `#aspect:` to listen to a specific aspect of a model. Changes will then trigger `#update`.!
  386. !MKSingleAspectView methodsFor: 'accessing'!
  387. aspect
  388. ^ aspect
  389. !
  390. aspect: aSelector
  391. aspect := aSelector
  392. !
  393. aspectValue
  394. ^ self valueForAspect: self aspect
  395. ! !
  396. !MKSingleAspectView methodsFor: 'defaults'!
  397. defaultControllerClass
  398. ^ MKSingleAspectController
  399. ! !
  400. !MKSingleAspectView methodsFor: 'updating'!
  401. update: anAnnouncement
  402. anAnnouncement aspect = self aspect ifTrue: [
  403. self update ]
  404. ! !
  405. !MKSingleAspectView class methodsFor: 'instance creation'!
  406. model: aModel aspect: aSelector
  407. ^ (self model: aModel)
  408. aspect: aSelector;
  409. yourself
  410. ! !
  411. MKLayoutView subclass: #MKDecorator
  412. instanceVariableNames: 'decorated'
  413. package: 'Moka-Core'!
  414. !MKDecorator commentStamp!
  415. I am root class of the decorator pattern in Moka.
  416. I am used to add rendering and/or behavior to other views.
  417. ## API
  418. To decorate a view, use the class-side `#decorate:` method.!
  419. !MKDecorator methodsFor: 'accessing'!
  420. children
  421. ^ { self decorated }
  422. !
  423. decorated
  424. ^ decorated
  425. !
  426. decorated: aView
  427. decorated := aView.
  428. self observeDecorated
  429. ! !
  430. !MKDecorator methodsFor: 'observing'!
  431. observeDecorated
  432. "Override in subclasses"
  433. ! !
  434. !MKDecorator methodsFor: 'rendering'!
  435. renderContentOn: html
  436. html with: self decorated
  437. ! !
  438. !MKDecorator class methodsFor: 'instance creation'!
  439. decorate: aView
  440. ^ self new
  441. decorated: aView;
  442. yourself
  443. ! !