Moka-Core.st 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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. tag
  176. ^ 'div'
  177. ! !
  178. !MKView methodsFor: 'actions'!
  179. blur
  180. root ifNotNil: [ root asJQuery blur ]
  181. !
  182. focus
  183. root ifNotNil: [ root asJQuery focus ]
  184. !
  185. remove
  186. "Removes the receiver from the DOM"
  187. root ifNotNil: [ root asJQuery remove ]
  188. ! !
  189. !MKView methodsFor: 'converting'!
  190. asJQuery
  191. ^ root asJQuery
  192. ! !
  193. !MKView methodsFor: 'defaults'!
  194. defaultControllerClass
  195. ^ MKController
  196. !
  197. defaultLayout
  198. ^ MKLayout new
  199. left: 0;
  200. top: 0;
  201. right: 0;
  202. bottom: 0;
  203. yourself
  204. ! !
  205. !MKView methodsFor: 'dom'!
  206. domPosition
  207. "Answer the position of the reciever in the page"
  208. | offset |
  209. offset := self asJQuery offset.
  210. ^ offset left @ offset top
  211. !
  212. domSize
  213. ^ self asJQuery width @ self asJQuery height
  214. ! !
  215. !MKView methodsFor: 'factory'!
  216. defaultController
  217. ^ self defaultControllerClass new
  218. ! !
  219. !MKView methodsFor: 'layout'!
  220. bottom
  221. ^ self layout bottom
  222. !
  223. bottom: aNumber
  224. self layout bottom: aNumber
  225. !
  226. centerX
  227. ^ self layout centerX
  228. !
  229. centerX: aNumber
  230. self layout centerX: aNumber
  231. !
  232. centerY
  233. ^ self layout centerY
  234. !
  235. centerY: aNumber
  236. self layout centerY: aNumber
  237. !
  238. height
  239. ^ self layout height
  240. !
  241. height: aNumber
  242. self layout height: aNumber
  243. !
  244. left
  245. ^ self layout left
  246. !
  247. left: aNumber
  248. self layout left: aNumber
  249. !
  250. right
  251. ^ self layout right
  252. !
  253. right: aNumber
  254. self layout right: aNumber
  255. !
  256. top
  257. ^ self layout top
  258. !
  259. top: aNumber
  260. self layout top: aNumber
  261. !
  262. width
  263. ^ self layout width
  264. !
  265. width: aNumber
  266. self layout width: aNumber
  267. ! !
  268. !MKView methodsFor: 'observing'!
  269. observeModel
  270. "No op. Override in subclasses"
  271. ! !
  272. !MKView methodsFor: 'private'!
  273. setupEventHandlers
  274. root
  275. onClick: [ :event | self controller onClick: event ];
  276. onDblClick: [ :event | self controller onDblClick: event ];
  277. onMouseEnter: [ :event | self controller onMouseEnter: event ];
  278. onMouseLeave: [ :event | self controller onMouseLeave: event ];
  279. onMouseOver: [ :event | self controller onMouseOver: event ];
  280. onMouseOut: [ :event | self controller onMouseOut: event ];
  281. onMouseMove: [ :event | self controller onMouseMove: event ];
  282. onKeyDown: [ :event | self controller onKeyDown: event ];
  283. onKeyUp: [ :event | self controller onKeyUp: event ];
  284. onKeyPress: [ :event | self controller onKeyPress: event ];
  285. onChange: [ :event | self controller onChange: event ]
  286. ! !
  287. !MKView methodsFor: 'rendering'!
  288. render
  289. "Append the receiver to the BODY element"
  290. self appendToJQuery: 'body' asJQuery
  291. !
  292. renderContentOn: html
  293. "Main rendering method, override in subclasses."
  294. !
  295. renderOn: html
  296. "Basic rendering method.
  297. Do not override this method, but `#renderContentOn:`"
  298. root := (html tag: self tag)
  299. class: self cssClass;
  300. style: self layout asCssString;
  301. yourself.
  302. root with: [ self renderContentOn: html ].
  303. self setupEventHandlers
  304. ! !
  305. !MKView methodsFor: 'updating'!
  306. update
  307. "Update the view's content. Override in subclasses to fine-tune updating"
  308. root ifNil: [ self error: 'The view has not been rendered yet' ].
  309. root asJQuery empty.
  310. [ :html | self renderContentOn: html ]
  311. appendToJQuery: root asJQuery
  312. ! !
  313. !MKView class methodsFor: 'instance creation'!
  314. model: aModel
  315. ^ self new
  316. model: aModel;
  317. yourself
  318. !
  319. model: aModel controller: aController
  320. ^ (self model: aModel)
  321. controller: aController;
  322. yourself
  323. ! !
  324. MKView subclass: #MKAspectsView
  325. instanceVariableNames: ''
  326. package: 'Moka-Core'!
  327. !MKAspectsView commentStamp!
  328. I am an abstract view which state depend on aspects of a model.!
  329. !MKAspectsView methodsFor: 'accessing'!
  330. valueForAspect: aSelector
  331. ^ self model perform: aSelector
  332. ! !
  333. !MKAspectsView methodsFor: 'defaults'!
  334. defaultControllerClass
  335. ^ MKAspectController
  336. ! !
  337. !MKAspectsView methodsFor: 'observing'!
  338. observeModel
  339. super observeModel.
  340. self model
  341. on: MKModelChanged
  342. send: 'update:'
  343. to: self
  344. ! !
  345. !MKAspectsView methodsFor: 'updating'!
  346. update: anAnnouncement
  347. "Override in subclasses to match the view's aspect(s)"
  348. ! !
  349. MKAspectsView subclass: #MKSingleAspectView
  350. instanceVariableNames: 'aspect'
  351. package: 'Moka-Core'!
  352. !MKSingleAspectView commentStamp!
  353. I am an abstract view which state depend on an `aspect` of a model.
  354. ##API
  355. - Use the `#aspect:` to listen to a specific aspect of a model. Changes will then trigger `#update`.!
  356. !MKSingleAspectView methodsFor: 'accessing'!
  357. aspect
  358. ^ aspect
  359. !
  360. aspect: aSelector
  361. aspect := aSelector
  362. !
  363. aspectValue
  364. ^ self valueForAspect: self aspect
  365. ! !
  366. !MKSingleAspectView methodsFor: 'defaults'!
  367. defaultControllerClass
  368. ^ MKSingleAspectController
  369. ! !
  370. !MKSingleAspectView methodsFor: 'updating'!
  371. update: anAnnouncement
  372. anAnnouncement aspect = self aspect ifTrue: [
  373. self update ]
  374. ! !
  375. !MKSingleAspectView class methodsFor: 'instance creation'!
  376. model: aModel aspect: aSelector
  377. ^ (self model: aModel)
  378. aspect: aSelector;
  379. yourself
  380. ! !
  381. MKView subclass: #MKDecorator
  382. instanceVariableNames: 'decorated'
  383. package: 'Moka-Core'!
  384. !MKDecorator commentStamp!
  385. I am root class of the decorator pattern in Moka.
  386. I am used to add rendering and/or behavior to other views.
  387. ## API
  388. To decorate a view, use the class-side `#decorate:` method.!
  389. !MKDecorator methodsFor: 'accessing'!
  390. decorated
  391. ^ decorated
  392. !
  393. decorated: aView
  394. decorated := aView
  395. ! !
  396. !MKDecorator methodsFor: 'rendering'!
  397. renderContentOn: html
  398. html with: self decorated
  399. ! !
  400. !MKDecorator class methodsFor: 'instance creation'!
  401. decorate: aView
  402. ^ self new
  403. decorated: aView;
  404. yourself
  405. ! !