Moka-Core.st 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. Smalltalk current 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: #MKModel
  77. instanceVariableNames: 'announcer'
  78. package: 'Moka-Core'!
  79. !MKModel commentStamp!
  80. I implement the Model part of the MVC pattern in Moka.
  81. I am the abstract superclass of all Moka model. The observer pattern is implemented through an `announcer` object.
  82. ## API
  83. - Listening
  84. Use `#on:do:` or `#on:send:to:` to listen to model changes
  85. - Triggering
  86. `#changed:` is the builtin method used to trigger `#update:` in views.
  87. Use `#announce:` in subclasses to trigger announcements to listeners.!
  88. !MKModel methodsFor: 'announcements'!
  89. announce: anAnnouncement
  90. announcer announce: anAnnouncement
  91. !
  92. changed: aSelector
  93. "Trigger `#update:` to all listening aspect views"
  94. self announce: (MKModelChanged aspect: aSelector)
  95. !
  96. on: anAnnouncement do: aBlock
  97. announcer on: anAnnouncement do: aBlock
  98. !
  99. on: anAnnouncement send: aSelector to: anObject
  100. announcer on: anAnnouncement send: aSelector to: anObject
  101. ! !
  102. !MKModel methodsFor: 'initialization'!
  103. initialize
  104. super initialize.
  105. announcer := Announcer new
  106. ! !
  107. Object subclass: #MKModelChanged
  108. instanceVariableNames: 'aspect'
  109. package: 'Moka-Core'!
  110. !MKModelChanged commentStamp!
  111. I am an announcement announced when a model is changed.!
  112. !MKModelChanged methodsFor: 'accessing'!
  113. aspect
  114. ^ aspect
  115. !
  116. aspect: aSelector
  117. aspect := aSelector
  118. ! !
  119. !MKModelChanged class methodsFor: 'instance creation'!
  120. aspect: aSelector
  121. ^ self new
  122. aspect: aSelector;
  123. yourself
  124. ! !
  125. Widget subclass: #MKView
  126. instanceVariableNames: 'controller model root layout extraCssClass'
  127. package: 'Moka-Core'!
  128. !MKView commentStamp!
  129. I implement the View part of the MVC pattern in Moka.
  130. ## API
  131. - Instance can be created with the `MKView class >> model:*` convenience methods
  132. - rendering is done through `#renderContentOn:`, to be overridden in concrete view classes
  133. - `#update` provide updating facility, refreshing the entire view
  134. - subclasses can override `#defaultControllerClass` to provide a default controller specific to a view
  135. - subclasses can override `#observeModel`
  136. - Extra css classes can be added with `#extraCssClass:`.!
  137. !MKView methodsFor: 'accessing'!
  138. controller
  139. "Answer the current receiver's controller.
  140. If no controller is installed yet, install the `defaultController`
  141. of the receiver and answer it."
  142. controller ifNil: [
  143. self controller: self defaultController ].
  144. ^ controller
  145. !
  146. controller: aController
  147. "Install `aController` to be the receiver's controller"
  148. controller := aController.
  149. aController
  150. view: self;
  151. model: self model
  152. !
  153. cssClass
  154. ^ String streamContents: [ :stream |
  155. stream << 'moka_view'.
  156. self extraCssClass ifNotEmpty: [
  157. stream << ' ' << self extraCssClass ] ]
  158. !
  159. extraCssClass
  160. ^ extraCssClass ifNil: [ '' ]
  161. !
  162. extraCssClass: aString
  163. extraCssClass := aString
  164. !
  165. layout
  166. ^ layout ifNil: [ layout := self defaultLayout ]
  167. !
  168. model
  169. ^ model
  170. !
  171. model: aModel
  172. model := aModel.
  173. self observeModel
  174. !
  175. position
  176. "Answer the position of the reciever in the page"
  177. ^ root ifNotNil: [
  178. | offset |
  179. offset := root asJQuery offset.
  180. offset left @ offset top ]
  181. !
  182. tag
  183. ^ 'div'
  184. ! !
  185. !MKView methodsFor: 'actions'!
  186. blur
  187. root ifNotNil: [ root asJQuery blur ]
  188. !
  189. focus
  190. root ifNotNil: [ root asJQuery focus ]
  191. !
  192. remove
  193. "Removes the receiver from the DOM"
  194. root ifNotNil: [ root asJQuery remove ]
  195. ! !
  196. !MKView methodsFor: 'defaults'!
  197. defaultControllerClass
  198. ^ MKController
  199. !
  200. defaultLayout
  201. ^ MKLayout new
  202. left: 0;
  203. top: 0;
  204. right: 0;
  205. bottom: 0;
  206. yourself
  207. ! !
  208. !MKView methodsFor: 'factory'!
  209. defaultController
  210. ^ self defaultControllerClass new
  211. ! !
  212. !MKView methodsFor: 'layout'!
  213. bottom: aNumber
  214. self layout bottom: aNumber
  215. !
  216. centerX: aNumber
  217. self layout centerX: aNumber
  218. !
  219. centerY: aNumber
  220. self layout centerY: aNumber
  221. !
  222. height: aNumber
  223. self layout height: aNumber
  224. !
  225. left: aNumber
  226. self layout left: aNumber
  227. !
  228. right: aNumber
  229. self layout right: aNumber
  230. !
  231. top: aNumber
  232. self layout top: aNumber
  233. !
  234. width: aNumber
  235. self layout width: aNumber
  236. ! !
  237. !MKView methodsFor: 'observing'!
  238. observeModel
  239. "No op. Override in subclasses"
  240. ! !
  241. !MKView methodsFor: 'private'!
  242. setupEventHandlers
  243. root
  244. onClick: [ :event | self controller onClick: event ];
  245. onDblClick: [ :event | self controller onDblClick: event ];
  246. onMouseEnter: [ :event | self controller onMouseEnter: event ];
  247. onMouseLeave: [ :event | self controller onMouseLeave: event ];
  248. onMouseOver: [ :event | self controller onMouseOver: event ];
  249. onMouseOut: [ :event | self controller onMouseOut: event ];
  250. onMouseMove: [ :event | self controller onMouseMove: event ];
  251. onKeyDown: [ :event | self controller onKeyDown: event ];
  252. onKeyUp: [ :event | self controller onKeyUp: event ];
  253. onKeyPress: [ :event | self controller onKeyPress: event ];
  254. onChange: [ :event | self controller onChange: event ]
  255. ! !
  256. !MKView methodsFor: 'rendering'!
  257. render
  258. "Append the receiver to the BODY element"
  259. self appendToJQuery: 'body' asJQuery
  260. !
  261. renderContentOn: html
  262. "Main rendering method, override in subclasses."
  263. !
  264. renderOn: html
  265. "Basic rendering method.
  266. Do not override this method, but `#renderContentOn:`"
  267. root := (html tag: self tag)
  268. class: self cssClass;
  269. style: self layout asCssString;
  270. yourself.
  271. root with: [ self renderContentOn: html ].
  272. self setupEventHandlers
  273. ! !
  274. !MKView methodsFor: 'updating'!
  275. update
  276. "Update the view's content. Override in subclasses to fine-tune updating"
  277. root ifNil: [ self error: 'The view has not been rendered yet' ].
  278. root asJQuery empty.
  279. [ :html | self renderContentOn: html ]
  280. appendToJQuery: root asJQuery
  281. ! !
  282. !MKView class methodsFor: 'instance creation'!
  283. model: aModel
  284. ^ self new
  285. model: aModel;
  286. yourself
  287. !
  288. model: aModel controller: aController
  289. ^ (self model: aModel)
  290. controller: aController;
  291. yourself
  292. ! !
  293. MKView subclass: #MKAspectsView
  294. instanceVariableNames: ''
  295. package: 'Moka-Core'!
  296. !MKAspectsView commentStamp!
  297. I am an abstract view which state depend on aspects of a model.!
  298. !MKAspectsView methodsFor: 'accessing'!
  299. valueForAspect: aSelector
  300. ^ self model perform: aSelector
  301. ! !
  302. !MKAspectsView methodsFor: 'defaults'!
  303. defaultControllerClass
  304. ^ MKAspectController
  305. ! !
  306. !MKAspectsView methodsFor: 'observing'!
  307. observeModel
  308. super observeModel.
  309. self model
  310. on: MKModelChanged
  311. send: 'update:'
  312. to: self
  313. ! !
  314. !MKAspectsView methodsFor: 'updating'!
  315. update: anAnnouncement
  316. "Override in subclasses to match the view's aspect(s)"
  317. ! !
  318. MKAspectsView subclass: #MKSingleAspectView
  319. instanceVariableNames: 'aspect'
  320. package: 'Moka-Core'!
  321. !MKSingleAspectView commentStamp!
  322. I am an abstract view which state depend on an `aspect` of a model.
  323. ##API
  324. - Use the `#aspect:` to listen to a specific aspect of a model. Changes will then trigger `#update`.!
  325. !MKSingleAspectView methodsFor: 'accessing'!
  326. aspect
  327. ^ aspect
  328. !
  329. aspect: aSelector
  330. aspect := aSelector
  331. !
  332. aspectValue
  333. ^ self valueForAspect: self aspect
  334. ! !
  335. !MKSingleAspectView methodsFor: 'defaults'!
  336. defaultControllerClass
  337. ^ MKSingleAspectController
  338. ! !
  339. !MKSingleAspectView methodsFor: 'updating'!
  340. update: anAnnouncement
  341. anAnnouncement aspect = self aspect ifTrue: [
  342. self update ]
  343. ! !
  344. !MKSingleAspectView class methodsFor: 'instance creation'!
  345. model: aModel aspect: aSelector
  346. ^ (self model: aModel)
  347. aspect: aSelector;
  348. yourself
  349. ! !