Smalltalk current createPackage: 'Moka-Core'!
Object subclass: #MKController
	instanceVariableNames: 'view model'
	package: 'Moka-Core'!
!MKController commentStamp!
I implement the Controller part of the MVC pattern in Moka.

I hold onto my `model` and `view`, set with `MKView >> controller:`.!

!MKController methodsFor: 'accessing'!

model
	^ model
!

model: aModel
	model := aModel
!

view
	^ view
!

view: aView
	view := aView
! !

!MKController methodsFor: 'actions'!

onChange: anEvent
!

onClick: anEvent
!

onDblClick: anEvent
!

onKeyDown: anEvent
!

onKeyPress: anEvent
!

onKeyUp: anEvent
!

onMouseEnter: anEvent
!

onMouseLeave: anEvent
!

onMouseMove: anEvent
!

onMouseOut: anEvent
!

onMouseOver: anEvent
! !

MKController subclass: #MKAspectsController
	instanceVariableNames: ''
	package: 'Moka-Core'!
!MKAspectsController commentStamp!
I am an abstract controller for performing one action using an `aspect` on a model.

## API

- Use `#aspect:` to plug a selector to be performed on the model
- Subclasses can either use `#performActionWith:` or `#performAction` to evaluate the `aspect` selector on the model with one or no argument.!

!MKAspectsController methodsFor: 'actions'!

performAspectAction: aSelector
	self model perform: aSelector
!

performAspectAction: aSelector with: anObject
	self model 
		perform: aSelector asMutator
		withArguments: { anObject }
! !

MKAspectsController subclass: #MKSingleAspectController
	instanceVariableNames: ''
	package: 'Moka-Core'!
!MKSingleAspectController commentStamp!
I am an abstract controller used with single aspect views.

My view must hold onto one aspect accessed with `#aspect`.!

!MKSingleAspectController methodsFor: 'actions'!

performAspectAction
	^ self performAspectAction: self view aspect
!

performAspectActionWith: anObject
	^ self 
		performAspectAction: self view aspect
		with: anObject
! !

Object subclass: #MKObservable
	instanceVariableNames: 'announcer'
	package: 'Moka-Core'!
!MKObservable commentStamp!
View models are typically subclasses of me. 

I implement the Observable part of the Observer pattern in Moka.

The observer pattern is implemented through an `announcer` object.

## API

- Listening

  Use `#on:do:` or `#on:send:to:` to listen to receiver changes

- Triggering

  `#changed:` is the builtin method used to trigger `#update:` in views.
  Use `#announce:` in subclasses to trigger announcements to listeners.!

!MKObservable methodsFor: 'announcements'!

announce: anAnnouncement
	announcer announce: anAnnouncement
!

changed: aSelector
	"Trigger `#update:` to all listening aspect views"
	
	self announce: (MKAspectChanged aspect: aSelector)
!

on: anAnnouncement do: aBlock
	announcer on: anAnnouncement do: aBlock
!

on: anAnnouncement send: aSelector to: anObject
	announcer on: anAnnouncement send: aSelector to: anObject
! !

!MKObservable methodsFor: 'initialization'!

initialize
	super initialize.
	announcer := Announcer new
! !

MKObservable subclass: #MKView
	instanceVariableNames: 'controller model root extraCssClass'
	package: 'Moka-Core'!
!MKView commentStamp!
I implement the View part of the MVC pattern in Moka.

## API
- Instance can be created with the `MKView class >> model:*` convenience methods
- rendering is done through `#renderContentOn:`, to be overridden in concrete view classes
- `#update` provide updating facility, refreshing the entire view
- subclasses can override `#defaultControllerClass` to provide a default controller specific to a view
- subclasses can override `#observeModel`
- Extra css classes can be added with `#extraCssClass:`.!

!MKView methodsFor: 'accessing'!

children
	"Answer all the sub-views of the receiver"
	^ #()
!

controller
	"Answer the current receiver's controller.
	If no controller is installed yet, install the `defaultController`
	of the receiver and answer it."
	
	controller ifNil: [ 
		self controller: self defaultController ].
	^ controller
!

controller: aController
	"Install `aController` to be the receiver's controller"
	
	controller := aController.
	aController 
		view: self;
		model: self model
!

cssClass
	^ String streamContents: [ :stream |
		stream << 'moka_view'.
		self extraCssClass ifNotEmpty: [
			stream << ' ' << self extraCssClass ] ]
!

cssStyle
	^ ''
!

extraCssClass
	^ extraCssClass ifNil: [ '' ]
!

extraCssClass: aString
	extraCssClass := aString
!

model
	^ model
!

model: aModel
	model := aModel.
	self observeModel
!

tag
	^ 'div'
! !

!MKView methodsFor: 'actions'!

blur
	root ifNotNil: [ root asJQuery blur ]
!

focus
	root ifNotNil: [ root asJQuery focus ]
!

remove
	"Removes the receiver from the DOM"
	root ifNotNil: [ root asJQuery remove ].
	
	self announce: (MKViewRemoved view: self)
!

resized
	"Action triggered when the view has been resized from the outside"
	
	self children do: [ :each | each resized ]
! !

!MKView methodsFor: 'adding'!

appendToBrush: aTagBrush
	self appendToJQuery: aTagBrush asJQuery
!

appendToJQuery: aJQuery
	self renderOn: (HTMLCanvas onJQuery: aJQuery)
! !

!MKView methodsFor: 'converting'!

asJQuery
	^ root asJQuery
! !

!MKView methodsFor: 'defaults'!

defaultControllerClass
	^ MKController
! !

!MKView methodsFor: 'dom'!

domPosition
	"Answer the position of the reciever in the page"
	
	| offset |
	offset := self asJQuery offset.
	^ offset left @ offset top
!

domSize
	^ self asJQuery width @ self asJQuery height
! !

!MKView methodsFor: 'factory'!

defaultController
	^ self defaultControllerClass new
! !

!MKView methodsFor: 'observing'!

observeModel
	"No op. Override in subclasses"
! !

!MKView methodsFor: 'private'!

setupEventHandlers
	root
		onClick: [ :event | self controller onClick: event ];
		onDblClick: [ :event | self controller onDblClick: event ];
		onMouseEnter: [ :event | self controller onMouseEnter: event ];
		onMouseLeave: [ :event | self controller onMouseLeave: event ];
		onMouseOver: [ :event | self controller onMouseOver: event ];
		onMouseOut: [ :event | self controller onMouseOut: event ];
		onMouseMove: [ :event | self controller onMouseMove: event ];
		onKeyDown: [ :event | self controller onKeyDown: event ];
		onKeyUp: [ :event | self controller onKeyUp: event ];
		onKeyPress: [ :event | self controller onKeyPress: event ];
		onChange: [ :event | self controller onChange: event ]
! !

!MKView methodsFor: 'rendering'!

render
	"Append the receiver to the BODY element"
	
	self appendToJQuery: 'body' asJQuery
!

renderContentOn: html
	"Main rendering method, override in subclasses."
!

renderOn: html
	"Basic rendering method.
	Do not override this method, but `#renderContentOn:`"
	
	root := (html tag: self tag)
		class: self cssClass;
		style: self cssStyle;
		yourself.
	root with: [ self renderContentOn: html ].
	
	self setupEventHandlers
! !

!MKView methodsFor: 'updating'!

update
	"Update the view's content. Override in subclasses to fine-tune updating"
	
	root ifNil: [ ^ self ].
	
	root asJQuery empty.
	[ :html | self renderContentOn: html ] 
		appendToJQuery: root asJQuery
! !

!MKView class methodsFor: 'instance creation'!

model: aModel
	^ self new
		model: aModel;
		yourself
!

model: aModel controller: aController
	^ (self model: aModel)
		controller: aController;
		yourself
! !

MKView subclass: #MKLayoutView
	instanceVariableNames: 'layout'
	package: 'Moka-Core'!
!MKLayoutView commentStamp!
I implement the View part of the MVC pattern in Moka.

## API
- Instance can be created with the `MKView class >> model:*` convenience methods
- rendering is done through `#renderContentOn:`, to be overridden in concrete view classes
- `#update` provide updating facility, refreshing the entire view
- subclasses can override `#defaultControllerClass` to provide a default controller specific to a view
- subclasses can override `#observeModel`
- Extra css classes can be added with `#extraCssClass:`.!

!MKLayoutView methodsFor: 'accessing'!

cssStyle
	^ self layout asCssString
!

layout
	^ layout ifNil: [ layout := self defaultLayout ]
! !

!MKLayoutView methodsFor: 'defaults'!

defaultLayout
	^ MKLayout new
		left: 0;
		top: 0;
		right: 0;
		bottom: 0;
		yourself
! !

!MKLayoutView methodsFor: 'layout'!

bottom
	^ self layout bottom
!

bottom: aNumber
	self layout bottom: aNumber
!

centerX
	^ self layout centerX
!

centerX: aNumber
	self layout centerX: aNumber
!

centerY
	^ self layout centerY
!

centerY: aNumber
	self layout centerY: aNumber
!

height
	^ self layout height
!

height: aNumber
	self layout height: aNumber
!

left
	^ self layout left
!

left: aNumber
	self layout left: aNumber
!

right
	^ self layout right
!

right: aNumber
	self layout right: aNumber
!

top
	^ self layout top
!

top: aNumber
	self layout top: aNumber
!

width
	^ self layout width
!

width: aNumber
	self layout width: aNumber
! !

!MKLayoutView class methodsFor: 'instance creation'!

model: aModel
	^ self new
		model: aModel;
		yourself
!

model: aModel controller: aController
	^ (self model: aModel)
		controller: aController;
		yourself
! !

MKLayoutView subclass: #MKAspectsView
	instanceVariableNames: ''
	package: 'Moka-Core'!
!MKAspectsView commentStamp!
I am an abstract view which state depend on aspects of a model.!

!MKAspectsView methodsFor: 'accessing'!

valueForAspect: aSelector
	^ self model perform: aSelector
! !

!MKAspectsView methodsFor: 'defaults'!

defaultControllerClass
	^ MKAspectController
! !

!MKAspectsView methodsFor: 'observing'!

observeModel
	super observeModel.
	
	self model
		on: MKAspectChanged
		send: 'update:'
		to: self
! !

!MKAspectsView methodsFor: 'updating'!

update: anAnnouncement
	"Override in subclasses to match the view's aspect(s)"
! !

MKAspectsView subclass: #MKSingleAspectView
	instanceVariableNames: 'aspect'
	package: 'Moka-Core'!
!MKSingleAspectView commentStamp!
I am an abstract view which state depend on an `aspect` of a model. 

##API

- Use the `#aspect:` to listen to a specific aspect of a model. Changes will then trigger `#update`.!

!MKSingleAspectView methodsFor: 'accessing'!

aspect
	^ aspect
!

aspect: aSelector
	aspect := aSelector
!

aspectValue
	^ self valueForAspect: self aspect
! !

!MKSingleAspectView methodsFor: 'defaults'!

defaultControllerClass
	^ MKSingleAspectController
! !

!MKSingleAspectView methodsFor: 'updating'!

update: anAnnouncement
	anAnnouncement aspect = self aspect ifTrue: [
		self update ]
! !

!MKSingleAspectView class methodsFor: 'instance creation'!

model: aModel aspect: aSelector
	^ (self model: aModel)
		aspect: aSelector;
		yourself
! !

MKLayoutView subclass: #MKDecorator
	instanceVariableNames: 'decorated'
	package: 'Moka-Core'!
!MKDecorator commentStamp!
I am root class of the decorator pattern in Moka. 

I am used to add rendering and/or behavior to other views.

## API

To decorate a view, use the class-side `#decorate:` method.!

!MKDecorator methodsFor: 'accessing'!

children
	^ { self decorated }
!

decorated
	^ decorated
!

decorated: aView
	decorated := aView.
	self observeDecorated
! !

!MKDecorator methodsFor: 'observing'!

observeDecorated
	"Override in subclasses"
! !

!MKDecorator methodsFor: 'rendering'!

renderContentOn: html
	html with: self decorated
! !

!MKDecorator class methodsFor: 'instance creation'!

decorate: aView
	^ self new
		decorated: aView;
		yourself
! !