瀏覽代碼

moka refactorings and first version of splitters

GNOME-OSTree builder 10 年之前
父節點
當前提交
44e726610e
共有 17 個文件被更改,包括 2514 次插入989 次删除
  1. 1 0
      bower.json
  2. 30 3
      css/moka.css
  3. 49 12
      css/moka.less
  4. 1 1
      index.html
  5. 134 0
      js/Moka-Announcements.js
  6. 89 56
      js/Moka-Controllers.js
  7. 432 339
      js/Moka-Core.js
  8. 451 3
      js/Moka-Decorators.js
  9. 136 136
      js/Moka-Examples.js
  10. 560 225
      js/Moka-Views.js
  11. 77 0
      st/Moka-Announcements.st
  12. 30 18
      st/Moka-Controllers.st
  13. 166 114
      st/Moka-Core.st
  14. 149 2
      st/Moka-Decorators.st
  15. 9 9
      st/Moka-Examples.st
  16. 199 70
      st/Moka-Views.st
  17. 1 1
      support/amber.js

+ 1 - 0
bower.json

@@ -13,6 +13,7 @@
     "jquery": "~1.8.3",
     "bootstrap": "http://getbootstrap.com/2.3.2/assets/bootstrap.zip",
     "jquery-tabby": "git://github.com/alanhogan/Tabby",
+	"jquery-mousewheel": "git://github.com/brandonaaron/jquery-mousewheel",
     "es5-shim": "~2.1.0",
     "codemirror": "~3.13.0",
     "showdown": "~0.3.1",

+ 30 - 3
css/moka.css

@@ -28,7 +28,7 @@
   left: 0;
   right: 0;
   bottom: 0;
-  zindex: 1000;
+  z-index: 1000;
   background: transparent;
 }
 .moka_view {
@@ -70,12 +70,12 @@
   border-color: #888888;
   border-style: solid;
 }
-.moka_view.mk_pane.mk_modal {
+.moka_view.mk_modal {
   z-index: 1001;
   background: transparent;
   border: 0 none;
 }
-.moka_view.mk_pane.mk_modal:focus {
+.moka_view.mk_modal:focus {
   outline: 0 none;
 }
 .moka_view.mk_scroll .mk_scroll_rail {
@@ -193,6 +193,7 @@
   background: -ms-linear-gradient(bottom, #dedede, #ffffff);
   background: -moz-linear-gradient(center bottom, #dedede 0%, #ffffff 100%);
   background: -o-linear-gradient(#ffffff, #dedede);
+  background: linear-gradient(#ffffff, #dedede);
 }
 .moka_view.mk_button:focus {
   outline: 0;
@@ -206,6 +207,7 @@
   background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
   background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
   background: -o-linear-gradient(#deeaf8, #9ec4eb);
+  background: linear-gradient(#deeaf8, #9ec4eb);
   text-shadow: 0 1px 0 #eeeeee;
 }
 .moka_view.mk_button.default:active {
@@ -322,6 +324,7 @@
   background: -ms-linear-gradient(bottom, #dedede, #ffffff);
   background: -moz-linear-gradient(center bottom, #dedede 0%, #ffffff 100%);
   background: -o-linear-gradient(#ffffff, #dedede);
+  background: linear-gradient(#ffffff, #dedede);
   -webkit-border-radius: 20px;
   -moz-border-radius: 20px;
   border-radius: 20px;
@@ -348,6 +351,7 @@
   background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
   background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
   background: -o-linear-gradient(#deeaf8, #9ec4eb);
+  background: linear-gradient(#deeaf8, #9ec4eb);
   text-shadow: 0 1px 0 #eeeeee;
 }
 .moka_view.mk_switch.default:active {
@@ -449,6 +453,7 @@
   background: -ms-linear-gradient(bottom, #74aae2, #9ec4eb);
   background: -moz-linear-gradient(center bottom, #74aae2 0%, #9ec4eb 100%);
   background: -o-linear-gradient(#9ec4eb, #74aae2);
+  background: linear-gradient(#9ec4eb, #74aae2);
   border-top: 1px solid #4a90d9;
   border-bottom: 1px solid #4a90d9;
   color: white;
@@ -492,6 +497,7 @@
   background: -ms-linear-gradient(bottom, #dedede, #ffffff);
   background: -moz-linear-gradient(center bottom, #dedede 0%, #ffffff 100%);
   background: -o-linear-gradient(#ffffff, #dedede);
+  background: linear-gradient(#ffffff, #dedede);
   text-align: left;
   padding: 0 8px;
 }
@@ -507,6 +513,7 @@
   background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
   background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
   background: -o-linear-gradient(#deeaf8, #9ec4eb);
+  background: linear-gradient(#deeaf8, #9ec4eb);
   text-shadow: 0 1px 0 #eeeeee;
 }
 .moka_view.mk_dropdown.default:active {
@@ -564,3 +571,23 @@
 .moka_view.mk_dropdown_pane .mk_dropdown_list:focus {
   border: 0;
 }
+.moka_view.mk_split_view .mk_splitter {
+  position: absolute;
+  border-width: 0;
+  z-index: 10;
+}
+.moka_view.mk_split_view.horizontal .mk_splitter {
+  width: 5px;
+  margin-left: -1px;
+  border-left: 1px solid #aaaaaa;
+  height: 100%;
+  float: left;
+  cursor: ew-resize;
+}
+.moka_view.mk_split_view.vertical .mk_splitter {
+  height: 5px;
+  margin-top: -1px;
+  width: 100%;
+  border-top: 1px solid #aaaaaa;
+  cursor: ns-resize;
+}

+ 49 - 12
css/moka.less

@@ -10,13 +10,15 @@
 				 color-stop(0, @start),
 				 color-stop(1, @stop));
     background: -ms-linear-gradient(bottom,
-				    @start,
-				    @stop);
+					 @start,
+					 @stop);
     background: -moz-linear-gradient(center bottom,
 				     @start 0%,
 				     @stop 100%);
     background: -o-linear-gradient(@stop,
 				   @start);
+    background: linear-gradient(@stop,
+				   @start);
 }
 .vertical-gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {
     background: @color;
@@ -31,6 +33,12 @@
     background: -moz-linear-gradient(right,
 				     @start 0%,
 				     @stop 100%);
+    background: -o-linear-gradient(right,
+				     @start 0%,
+				     @stop 100%);
+    background: linear-gradient(right,
+				     @start 0%,
+				     @stop 100%);
 }
 .linear-gradient(@arguments) {
     background: -webkit-linear-gradient(@arguments);
@@ -114,14 +122,14 @@
     display: block;
     overflow: hidden;
 }
- 
+
 .mk_overlay {
     position: fixed;
     top: 0;
     left: 0;
     right: 0;
     bottom: 0;
-    zindex: 1000;
+    z-index: 1000;
     background: transparent;
 }
 
@@ -165,15 +173,15 @@
 	    border-color: @dark;
 	    border-style: solid;
 	}
+    }
 
-	&.mk_modal {
-	    z-index: 1001;
-	    background: transparent;
-	    border: 0 none;
+    &.mk_modal {
+	z-index: 1001;
+	background: transparent;
+	border: 0 none;
 
-	    &:focus {
-		outline: 0 none;
-	    }
+	&:focus {
+	    outline: 0 none;
 	}
     }
 
@@ -377,7 +385,7 @@
 	}
     }
 
-// Dropdowns
+    // Dropdowns
 
     &.mk_dropdown {
 	.mk_button;
@@ -426,4 +434,33 @@
 	    }
 	}
     }
+
+
+    // Split views
+
+    &.mk_split_view {
+
+	.mk_splitter {
+	    position: absolute;
+	    border-width: 0;
+	    z-index: 10;
+	}
+
+	&.horizontal .mk_splitter {
+	    width: 5px;
+	    margin-left: -1px;
+	    border-left: 1px solid @grey;
+	    height: 100%;
+	    float: left;
+	    cursor: ew-resize;
+	}
+
+	&.vertical .mk_splitter {
+	    height: 5px;
+	    margin-top: -1px;
+	    width: 100%;
+	    border-top: 1px solid @grey;
+	    cursor: ns-resize;
+	}
+    }
 }

+ 1 - 1
index.html

@@ -13,7 +13,7 @@
 <body>
 <script type='text/javascript'>
     require(
-        ["amber/devel", "amber_core/Moka-Core", "amber_core/Moka-Controllers", "amber_core/Moka-Views", "amber_core/Moka-Decorators", "amber_core/Moka-Layouts", "amber_core/Moka-Examples"],
+        ["amber/devel", "amber_core/Moka-Core", "amber_core/Moka-Announcements", "amber_core/Moka-Controllers", "amber_core/Moka-Views", "amber_core/Moka-Decorators", "amber_core/Moka-Layouts", "amber_core/Moka-Examples"],
         function (smalltalk) {
             smalltalk.defaultAmdNamespace = "amber_core";
             smalltalk.initialize();

+ 134 - 0
js/Moka-Announcements.js

@@ -0,0 +1,134 @@
+define("amber_core/Moka-Announcements", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_core/Kernel-Objects"], function(smalltalk,nil,_st){
+smalltalk.addPackage('Moka-Announcements');
+smalltalk.packages["Moka-Announcements"].transport = {"type":"amd","amdNamespace":"amber_core"};
+
+smalltalk.addClass('MKAnnouncement', smalltalk.Object, [], 'Moka-Announcements');
+smalltalk.MKAnnouncement.comment="I am the root class of all announcements sent in Moka.";
+
+
+smalltalk.addClass('MKAspectChanged', smalltalk.MKAnnouncement, ['aspect'], 'Moka-Announcements');
+smalltalk.MKAspectChanged.comment="I am announced whenever an `aspect` is changed.\x0a\x0a## API\x0a\x0aCreate instances using the class-side method `#aspect:`";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "aspect",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@aspect"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"aspect",{},smalltalk.MKAspectChanged)})},
+args: [],
+source: "aspect\x0a\x09^ aspect",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKAspectChanged);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "aspect:",
+category: 'accessing',
+fn: function (aSelector){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@aspect"]=aSelector;
+return self}, function($ctx1) {$ctx1.fill(self,"aspect:",{aSelector:aSelector},smalltalk.MKAspectChanged)})},
+args: ["aSelector"],
+source: "aspect: aSelector\x0a\x09aspect := aSelector",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKAspectChanged);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "aspect:",
+category: 'instance creation',
+fn: function (aSelector){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._aspect_(aSelector);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"aspect:",{aSelector:aSelector},smalltalk.MKAspectChanged.klass)})},
+args: ["aSelector"],
+source: "aspect: aSelector\x0a\x09^ self new\x0a\x09\x09aspect: aSelector;\x0a\x09\x09yourself",
+messageSends: ["aspect:", "new", "yourself"],
+referencedClasses: []
+}),
+smalltalk.MKAspectChanged.klass);
+
+
+smalltalk.addClass('MKViewAnnouncement', smalltalk.MKAnnouncement, ['view'], 'Moka-Announcements');
+smalltalk.MKViewAnnouncement.comment="I am the root class of all viewn announcements. I hold a `view` object.\x0a\x0a## API\x0a\x0aCreate instance with the class-side method `#view:`";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "view",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@view"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"view",{},smalltalk.MKViewAnnouncement)})},
+args: [],
+source: "view\x0a\x09^ view",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKViewAnnouncement);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "view:",
+category: 'accessing',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@view"]=aView;
+return self}, function($ctx1) {$ctx1.fill(self,"view:",{aView:aView},smalltalk.MKViewAnnouncement)})},
+args: ["aView"],
+source: "view: aView\x0a\x09view := aView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKViewAnnouncement);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "view:",
+category: 'instance creation',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._view_(aView);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"view:",{aView:aView},smalltalk.MKViewAnnouncement.klass)})},
+args: ["aView"],
+source: "view: aView\x0a\x09^ self new\x0a\x09\x09view: aView;\x0a\x09\x09yourself",
+messageSends: ["view:", "new", "yourself"],
+referencedClasses: []
+}),
+smalltalk.MKViewAnnouncement.klass);
+
+
+smalltalk.addClass('MKViewRemoved', smalltalk.MKViewAnnouncement, [], 'Moka-Announcements');
+smalltalk.MKViewRemoved.comment="I am announced when a view gets removed from the DOM.";
+
+
+smalltalk.addClass('MKViewScroll', smalltalk.MKViewAnnouncement, [], 'Moka-Announcements');
+smalltalk.MKViewScroll.comment="I am often used in conjunction with `MKScrollDecorator`.\x0a\x0aI am announced when a view's scroll changed programatically";
+
+});

+ 89 - 56
js/Moka-Controllers.js

@@ -200,6 +200,7 @@ smalltalk.MKCheckboxController);
 
 
 smalltalk.addClass('MKDropdownController', smalltalk.MKAspectsController, [], 'Moka-Controllers');
+smalltalk.MKDropdownController.comment="I am the default controller for `MKDropdownView`.";
 smalltalk.addMethod(
 smalltalk.method({
 selector: "onClick:",
@@ -587,7 +588,7 @@ smalltalk.MKDropdownListController);
 
 
 
-smalltalk.addClass('MKModalPaneController', smalltalk.MKSingleAspectController, [], 'Moka-Controllers');
+smalltalk.addClass('MKModalController', smalltalk.MKSingleAspectController, [], 'Moka-Controllers');
 smalltalk.addMethod(
 smalltalk.method({
 selector: "onClick:",
@@ -600,13 +601,13 @@ $1=_st(self._view())._closeOnClick();
 if(smalltalk.assert($1)){
 self._removeView();
 };
-return self}, function($ctx1) {$ctx1.fill(self,"onClick:",{anEvent:anEvent},smalltalk.MKModalPaneController)})},
+return self}, function($ctx1) {$ctx1.fill(self,"onClick:",{anEvent:anEvent},smalltalk.MKModalController)})},
 args: ["anEvent"],
 source: "onClick: anEvent\x0a\x09self view closeOnClick ifTrue: [ self removeView ]",
 messageSends: ["ifTrue:", "closeOnClick", "view", "removeView"],
 referencedClasses: []
 }),
-smalltalk.MKModalPaneController);
+smalltalk.MKModalController);
 
 smalltalk.addMethod(
 smalltalk.method({
@@ -616,29 +617,32 @@ fn: function (anEvent){
 var self=this;
 function $String(){return smalltalk.String||(typeof String=="undefined"?nil:String)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$1,$3,$4;
-$2=_st(anEvent)._keyCode();
+var $1,$3,$2,$4,$5;
+$1=_st(self._view())._closeOnEnter();
+if(smalltalk.assert($1)){
+$3=_st(anEvent)._keyCode();
 $ctx1.sendIdx["keyCode"]=1;
-$1=_st($2).__eq((27));
+$2=_st($3).__eq(_st(_st($String())._cr())._asciiValue());
 $ctx1.sendIdx["="]=1;
-if(smalltalk.assert($1)){
+if(smalltalk.assert($2)){
 self._removeView();
 $ctx1.sendIdx["removeView"]=1;
+_st(anEvent)._stopPropagation();
+$4=_st(anEvent)._preventDefault();
+$4;
 };
-$3=_st(self._view())._closeOnEnter();
-if(smalltalk.assert($3)){
-$4=_st(_st(anEvent)._keyCode()).__eq(_st(_st($String())._cr())._asciiValue());
-if(smalltalk.assert($4)){
-self._removeView();
 };
+$5=_st(_st(anEvent)._keyCode()).__eq((27));
+if(smalltalk.assert($5)){
+self._removeView();
 };
-return self}, function($ctx1) {$ctx1.fill(self,"onKeyDown:",{anEvent:anEvent},smalltalk.MKModalPaneController)})},
+return self}, function($ctx1) {$ctx1.fill(self,"onKeyDown:",{anEvent:anEvent},smalltalk.MKModalController)})},
 args: ["anEvent"],
-source: "onKeyDown: anEvent\x0a\x09\x22ESC\x22\x0a\x09anEvent keyCode = 27 ifTrue: [\x0a\x09\x09self removeView ].\x0a\x09\x09\x0a\x09self view closeOnEnter ifTrue: [\x0a\x09\x09anEvent keyCode = String cr asciiValue ifTrue: [ \x0a\x09\x09\x09self removeView ] ]",
-messageSends: ["ifTrue:", "=", "keyCode", "removeView", "closeOnEnter", "view", "asciiValue", "cr"],
+source: "onKeyDown: anEvent\x0a\x09self view closeOnEnter ifTrue: [\x0a\x09\x09anEvent keyCode = String cr asciiValue ifTrue: [\x0a\x09\x09\x09self removeView.\x0a\x09\x09\x09anEvent \x0a\x09\x09\x09\x09stopPropagation;\x0a\x09\x09\x09\x09preventDefault ] ].\x0a\x09\x0a\x09\x22ESC\x22\x0a\x09anEvent keyCode = 27 ifTrue: [\x0a\x09\x09self removeView ]",
+messageSends: ["ifTrue:", "closeOnEnter", "view", "=", "keyCode", "asciiValue", "cr", "removeView", "stopPropagation", "preventDefault"],
 referencedClasses: ["String"]
 }),
-smalltalk.MKModalPaneController);
+smalltalk.MKModalController);
 
 smalltalk.addMethod(
 smalltalk.method({
@@ -648,13 +652,13 @@ fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 _st(_st(self._view())._overlay())._remove();
-return self}, function($ctx1) {$ctx1.fill(self,"removeView",{},smalltalk.MKModalPaneController)})},
+return self}, function($ctx1) {$ctx1.fill(self,"removeView",{},smalltalk.MKModalController)})},
 args: [],
 source: "removeView\x0a\x09self view overlay remove",
 messageSends: ["remove", "overlay", "view"],
 referencedClasses: []
 }),
-smalltalk.MKModalPaneController);
+smalltalk.MKModalController);
 
 
 
@@ -812,6 +816,23 @@ smalltalk.MKRepeater);
 
 
 smalltalk.addClass('MKScrollController', smalltalk.MKController, [], 'Moka-Controllers');
+smalltalk.MKScrollController.comment="I am the default controller for `MKScrollDecorator`.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onDecoratedScroll",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(self._view())._updateScrollbarsPosition();
+return self}, function($ctx1) {$ctx1.fill(self,"onDecoratedScroll",{},smalltalk.MKScrollController)})},
+args: [],
+source: "onDecoratedScroll\x0a\x09self view updateScrollbarsPosition",
+messageSends: ["updateScrollbarsPosition", "view"],
+referencedClasses: []
+}),
+smalltalk.MKScrollController);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "onHorizontalDrag:",
@@ -819,76 +840,88 @@ category: 'actions',
 fn: function (anEvent){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-_st(_st(_st(_st(self._view())._decorated())._asJQuery())._get_((0)))._at_put_("scrollLeft",_st(self._position())._x());
+var $4,$3,$2,$1;
+$4=self._view();
+$ctx1.sendIdx["view"]=1;
+$3=_st($4)._decorated();
+$2=_st($3)._asJQuery();
+$1=_st($2)._get_((0));
+_st($1)._at_put_("scrollLeft",_st(_st(self._view())._domScrollPosition())._x());
 return self}, function($ctx1) {$ctx1.fill(self,"onHorizontalDrag:",{anEvent:anEvent},smalltalk.MKScrollController)})},
 args: ["anEvent"],
-source: "onHorizontalDrag: anEvent\x0a\x09(self view decorated asJQuery get: 0) at: 'scrollLeft' put: self position x",
-messageSends: ["at:put:", "get:", "asJQuery", "decorated", "view", "x", "position"],
+source: "onHorizontalDrag: anEvent\x0a\x09(self view decorated asJQuery get: 0) at: 'scrollLeft' put: self view domScrollPosition x",
+messageSends: ["at:put:", "get:", "asJQuery", "decorated", "view", "x", "domScrollPosition"],
 referencedClasses: []
 }),
 smalltalk.MKScrollController);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "onVerticalDrag:",
+selector: "onMousewheel:",
 category: 'actions',
 fn: function (anEvent){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $1,$3,$2;
-$1=console;
-$3=self._position();
-$ctx1.sendIdx["position"]=1;
-$2=_st($3)._asString();
-_st($1)._log_($2);
-_st(_st(_st(_st(self._view())._decorated())._asJQuery())._get_((0)))._at_put_("scrollTop",_st(self._position())._y());
-return self}, function($ctx1) {$ctx1.fill(self,"onVerticalDrag:",{anEvent:anEvent},smalltalk.MKScrollController)})},
+var $2,$1,$3,$4,$6,$5;
+$2=_st(anEvent)._deltaY();
+$ctx1.sendIdx["deltaY"]=1;
+$1=_st($2).__tild_eq((0));
+$ctx1.sendIdx["~="]=1;
+if(smalltalk.assert($1)){
+$3=self._view();
+$ctx1.sendIdx["view"]=1;
+$4=_st(_st(anEvent)._deltaY()).__star((10));
+$ctx1.sendIdx["*"]=1;
+_st($3)._scrollDeltaY_($4);
+};
+$6=_st(anEvent)._deltaX();
+$ctx1.sendIdx["deltaX"]=1;
+$5=_st($6).__tild_eq((0));
+if(smalltalk.assert($5)){
+_st(self._view())._scrollDeltaX_(_st(_st(anEvent)._deltaX()).__star((10)));
+};
+return self}, function($ctx1) {$ctx1.fill(self,"onMousewheel:",{anEvent:anEvent},smalltalk.MKScrollController)})},
 args: ["anEvent"],
-source: "onVerticalDrag: anEvent\x0a\x09console log: self position asString.\x0a\x09(self view decorated asJQuery get: 0) at: 'scrollTop' put: self position y",
-messageSends: ["log:", "asString", "position", "at:put:", "get:", "asJQuery", "decorated", "view", "y"],
+source: "onMousewheel: anEvent\x0a\x09anEvent deltaY ~= 0 ifTrue: [\x0a\x09\x09self view scrollDeltaY: anEvent deltaY * 10 ].\x0a\x09\x0a\x09anEvent deltaX ~= 0 ifTrue: [\x0a\x09\x09self view scrollDeltaX: anEvent deltaX * 10 ]",
+messageSends: ["ifTrue:", "~=", "deltaY", "scrollDeltaY:", "view", "*", "deltaX", "scrollDeltaX:"],
 referencedClasses: []
 }),
 smalltalk.MKScrollController);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "position",
-category: 'accessing',
+selector: "onResize",
+category: 'actions',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=_st(_st(self._view())._domDecoratedSize()).__star(self._scrollPercent());
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"position",{},smalltalk.MKScrollController)})},
+_st(self._view())._resized();
+return self}, function($ctx1) {$ctx1.fill(self,"onResize",{},smalltalk.MKScrollController)})},
 args: [],
-source: "position\x0a\x09^ self view domDecoratedSize * self scrollPercent",
-messageSends: ["*", "domDecoratedSize", "view", "scrollPercent"],
+source: "onResize\x0a\x09self view resized",
+messageSends: ["resized", "view"],
 referencedClasses: []
 }),
 smalltalk.MKScrollController);
 
 smalltalk.addMethod(
 smalltalk.method({
-selector: "scrollPercent",
-category: 'accessing',
-fn: function (){
+selector: "onVerticalDrag:",
+category: 'actions',
+fn: function (anEvent){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $3,$2,$6,$5,$4,$1;
-$3=self._view();
+var $4,$3,$2,$1;
+$4=self._view();
 $ctx1.sendIdx["view"]=1;
-$2=_st($3)._domScrollPosition();
-$6=self._view();
-$ctx1.sendIdx["view"]=2;
-$5=_st($6)._domSize();
-$4=_st($5).__minus(_st(self._view())._domScrollbarSize());
-$1=_st($2).__slash($4);
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"scrollPercent",{},smalltalk.MKScrollController)})},
-args: [],
-source: "scrollPercent\x0a\x09^ self view domScrollPosition / (self view domSize - self view domScrollbarSize)",
-messageSends: ["/", "domScrollPosition", "view", "-", "domSize", "domScrollbarSize"],
+$3=_st($4)._decorated();
+$2=_st($3)._asJQuery();
+$1=_st($2)._get_((0));
+_st($1)._at_put_("scrollTop",_st(_st(self._view())._domScrollPosition())._y());
+return self}, function($ctx1) {$ctx1.fill(self,"onVerticalDrag:",{anEvent:anEvent},smalltalk.MKScrollController)})},
+args: ["anEvent"],
+source: "onVerticalDrag: anEvent\x0a\x09(self view decorated asJQuery get: 0) at: 'scrollTop' put: self view domScrollPosition y",
+messageSends: ["at:put:", "get:", "asJQuery", "decorated", "view", "y", "domScrollPosition"],
 referencedClasses: []
 }),
 smalltalk.MKScrollController);

File diff suppressed because it is too large
+ 432 - 339
js/Moka-Core.js


+ 451 - 3
js/Moka-Decorators.js

@@ -2,7 +2,207 @@ define("amber_core/Moka-Decorators", ["amber_vm/smalltalk", "amber_vm/nil", "amb
 smalltalk.addPackage('Moka-Decorators');
 smalltalk.packages["Moka-Decorators"].transport = {"type":"amd","amdNamespace":"amber_core"};
 
+smalltalk.addClass('MKModalDecorator', smalltalk.MKDecorator, ['overlay', 'closeOnEnter', 'closeOnClick'], 'Moka-Decorators');
+smalltalk.MKModalDecorator.comment="I render my `decorated` view as a modal pane.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "closeOnClick",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@closeOnClick"];
+if(($receiver = $2) == nil || $receiver == null){
+$1=false;
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"closeOnClick",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "closeOnClick\x0a\x09^ closeOnClick ifNil: [ false ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "closeOnClick:",
+category: 'accessing',
+fn: function (aBoolean){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@closeOnClick"]=aBoolean;
+return self}, function($ctx1) {$ctx1.fill(self,"closeOnClick:",{aBoolean:aBoolean},smalltalk.MKModalDecorator)})},
+args: ["aBoolean"],
+source: "closeOnClick: aBoolean\x0a\x09closeOnClick := aBoolean",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "closeOnEnter",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@closeOnEnter"];
+if(($receiver = $2) == nil || $receiver == null){
+$1=false;
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"closeOnEnter",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "closeOnEnter\x0a\x09^ closeOnEnter ifNil: [ false ]",
+messageSends: ["ifNil:"],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "closeOnEnter:",
+category: 'accessing',
+fn: function (aBoolean){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@closeOnEnter"]=aBoolean;
+return self}, function($ctx1) {$ctx1.fill(self,"closeOnEnter:",{aBoolean:aBoolean},smalltalk.MKModalDecorator)})},
+args: ["aBoolean"],
+source: "closeOnEnter: aBoolean\x0a\x09closeOnEnter := aBoolean",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(smalltalk.MKModalDecorator.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" mk_modal");
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "cssClass\x0a\x09^ super cssClass, ' mk_modal'",
+messageSends: [",", "cssClass"],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "defaultControllerClass",
+category: 'defaults',
+fn: function (){
+var self=this;
+function $MKModalController(){return smalltalk.MKModalController||(typeof MKModalController=="undefined"?nil:MKModalController)}
+return smalltalk.withContext(function($ctx1) { 
+return $MKModalController();
+}, function($ctx1) {$ctx1.fill(self,"defaultControllerClass",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "defaultControllerClass\x0a\x09^ MKModalController",
+messageSends: [],
+referencedClasses: ["MKModalController"]
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "defaultLayout",
+category: 'defaults',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=smalltalk.MKModalDecorator.superclass.fn.prototype._defaultLayout.apply(_st(self), []);
+_st($2)._centerY_((0));
+_st($2)._centerX_((0));
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"defaultLayout",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "defaultLayout\x0a\x09^ super defaultLayout\x0a\x09\x09centerY: 0;\x0a\x09\x09centerX: 0;\x22\x0a\x09\x09width: 300;\x0a\x09\x09height: 200;\x22\x0a\x09\x09yourself",
+messageSends: ["centerY:", "defaultLayout", "centerX:", "yourself"],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "overlay",
+category: 'accessing',
+fn: function (){
+var self=this;
+function $MKOverlayView(){return smalltalk.MKOverlayView||(typeof MKOverlayView=="undefined"?nil:MKOverlayView)}
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@overlay"];
+if(($receiver = $2) == nil || $receiver == null){
+self["@overlay"]=_st($MKOverlayView())._childView_(self);
+$1=self["@overlay"];
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"overlay",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "overlay\x0a\x09^ overlay ifNil: [ overlay := MKOverlayView childView: self ]",
+messageSends: ["ifNil:", "childView:"],
+referencedClasses: ["MKOverlayView"]
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.MKModalDecorator.superclass.fn.prototype._renderOn_.apply(_st(self), [html]);
+_st(self["@root"])._at_put_("tabindex","0");
+_st(_st(self["@root"])._asJQuery())._focus();
+_st(html)._with_(self._overlay());
+return self}, function($ctx1) {$ctx1.fill(self,"renderOn:",{html:html},smalltalk.MKModalDecorator)})},
+args: ["html"],
+source: "renderOn: html\x0a\x09super renderOn: html.\x0a\x09root at: 'tabindex' put: '0'.\x0a\x09root asJQuery focus.\x0a\x09html with: self overlay",
+messageSends: ["renderOn:", "at:put:", "focus", "asJQuery", "with:", "overlay"],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "zindex",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return (1001);
+}, function($ctx1) {$ctx1.fill(self,"zindex",{},smalltalk.MKModalDecorator)})},
+args: [],
+source: "zindex\x0a\x09^ 1001",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKModalDecorator);
+
+
+
 smalltalk.addClass('MKScrollDecorator', smalltalk.MKDecorator, ['verticalScrollbar', 'horizontalScrollbar'], 'Moka-Decorators');
+smalltalk.MKScrollDecorator.comment="I decorate a view adding scrollbars around it.\x0a\x0aThe `decorated` view can send `MKViewScrolled` announcement to update the scrollbars position.";
 smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClass",
@@ -80,6 +280,24 @@ referencedClasses: []
 }),
 smalltalk.MKScrollDecorator);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domScrollPercent",
+category: 'dom',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._domScrollbarPosition()).__slash(_st(self._domSize()).__minus(self._domScrollbarSize()));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domScrollPercent",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "domScrollPercent\x0a\x09^ self domScrollbarPosition / (self domSize - self domScrollbarSize)",
+messageSends: ["/", "domScrollbarPosition", "-", "domSize", "domScrollbarSize"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "domScrollPosition",
@@ -87,6 +305,24 @@ category: 'dom',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(self._domDecoratedSize()).__minus(self._domSize())).__star(self._domScrollPercent());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domScrollPosition",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "domScrollPosition\x0a\x09^ (self domDecoratedSize - self domSize) * self domScrollPercent",
+messageSends: ["*", "-", "domDecoratedSize", "domSize", "domScrollPercent"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domScrollbarPosition",
+category: 'dom',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
 var $4,$3,$2,$1;
 $4=_st(self["@horizontalScrollbar"])._asJQuery();
 $ctx1.sendIdx["asJQuery"]=1;
@@ -95,9 +331,9 @@ $ctx1.sendIdx["position"]=1;
 $2=_st($3)._left();
 $1=_st($2).__at(_st(_st(_st(self["@verticalScrollbar"])._asJQuery())._position())._top());
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"domScrollPosition",{},smalltalk.MKScrollDecorator)})},
+}, function($ctx1) {$ctx1.fill(self,"domScrollbarPosition",{},smalltalk.MKScrollDecorator)})},
 args: [],
-source: "domScrollPosition\x0a\x09^ horizontalScrollbar asJQuery position left @ verticalScrollbar asJQuery position top",
+source: "domScrollbarPosition\x0a\x09^ horizontalScrollbar asJQuery position left @ verticalScrollbar asJQuery position top",
 messageSends: ["@", "left", "position", "asJQuery", "top"],
 referencedClasses: []
 }),
@@ -160,6 +396,41 @@ referencedClasses: []
 }),
 smalltalk.MKScrollDecorator);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "horizontalScrollbar",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@horizontalScrollbar"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"horizontalScrollbar",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "horizontalScrollbar\x0a\x09^ horizontalScrollbar",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "observeDecorated",
+category: 'observing',
+fn: function (){
+var self=this;
+function $MKViewScroll(){return smalltalk.MKViewScroll||(typeof MKViewScroll=="undefined"?nil:MKViewScroll)}
+return smalltalk.withContext(function($ctx1) { 
+_st(self._decorated())._on_send_to_($MKViewScroll(),"onDecoratedScroll",self._controller());
+return self}, function($ctx1) {$ctx1.fill(self,"observeDecorated",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "observeDecorated\x0a\x09self decorated \x0a\x09\x09on: MKViewScroll \x0a\x09\x09send: #onDecoratedScroll\x0a\x09\x09to: self controller",
+messageSends: ["on:send:to:", "decorated", "controller"],
+referencedClasses: ["MKViewScroll"]
+}),
+smalltalk.MKScrollDecorator);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderContentOn:",
@@ -214,6 +485,118 @@ referencedClasses: []
 }),
 smalltalk.MKScrollDecorator);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "resized",
+category: 'actions',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.MKScrollDecorator.superclass.fn.prototype._resized.apply(_st(self), []);
+self._updateScrollbars();
+return self}, function($ctx1) {$ctx1.fill(self,"resized",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "resized\x0a\x09super resized.\x0a\x09self updateScrollbars",
+messageSends: ["resized", "updateScrollbars"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollDeltaX:",
+category: 'actions',
+fn: function (aNumber){
+var self=this;
+var scrollbar,left,maxLeft;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+scrollbar=_st(self._horizontalScrollbar())._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$1=_st(self._domSize())._x();
+$ctx1.sendIdx["x"]=1;
+maxLeft=_st($1).__minus(_st(scrollbar)._width());
+left=_st(_st(_st(_st(_st(scrollbar)._position())._left()).__plus(aNumber))._max_((0)))._min_(maxLeft);
+_st(scrollbar)._css_put_("left",left);
+_st(_st(_st(self._decorated())._asJQuery())._get_((0)))._at_put_("scrollLeft",_st(self._domScrollPosition())._x());
+return self}, function($ctx1) {$ctx1.fill(self,"scrollDeltaX:",{aNumber:aNumber,scrollbar:scrollbar,left:left,maxLeft:maxLeft},smalltalk.MKScrollDecorator)})},
+args: ["aNumber"],
+source: "scrollDeltaX: aNumber\x0a\x09| scrollbar left maxLeft |\x0a\x09scrollbar := self horizontalScrollbar asJQuery.\x0a\x09maxLeft := self domSize x - scrollbar width.\x0a\x09left := ((scrollbar position left + aNumber) max: 0) min: maxLeft.\x0a\x09scrollbar css: 'left' put: left.\x0a\x09(self decorated asJQuery get: 0) at: 'scrollLeft' put: self domScrollPosition x",
+messageSends: ["asJQuery", "horizontalScrollbar", "-", "x", "domSize", "width", "min:", "max:", "+", "left", "position", "css:put:", "at:put:", "get:", "decorated", "domScrollPosition"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollDeltaY:",
+category: 'actions',
+fn: function (aNumber){
+var self=this;
+var scrollbar,top,maxTop;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+scrollbar=_st(self._verticalScrollbar())._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$1=_st(self._domSize())._y();
+$ctx1.sendIdx["y"]=1;
+maxTop=_st($1).__minus(_st(scrollbar)._height());
+$ctx1.sendIdx["-"]=1;
+top=_st(_st(_st(_st(_st(scrollbar)._position())._top()).__minus(aNumber))._max_((0)))._min_(maxTop);
+_st(scrollbar)._css_put_("top",top);
+_st(_st(_st(self._decorated())._asJQuery())._get_((0)))._at_put_("scrollTop",_st(self._domScrollPosition())._y());
+return self}, function($ctx1) {$ctx1.fill(self,"scrollDeltaY:",{aNumber:aNumber,scrollbar:scrollbar,top:top,maxTop:maxTop},smalltalk.MKScrollDecorator)})},
+args: ["aNumber"],
+source: "scrollDeltaY: aNumber\x0a\x09| scrollbar top maxTop |\x0a\x09scrollbar := self verticalScrollbar asJQuery.\x0a\x09maxTop := self domSize y - scrollbar height.\x0a\x09top := ((scrollbar position top - aNumber) max: 0) min: maxTop.\x0a\x09scrollbar css: 'top' put: top.\x0a\x09(self decorated asJQuery get: 0) at: 'scrollTop' put: self domScrollPosition y",
+messageSends: ["asJQuery", "verticalScrollbar", "-", "y", "domSize", "height", "min:", "max:", "top", "position", "css:put:", "at:put:", "get:", "decorated", "domScrollPosition"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollPercent",
+category: 'accessing',
+fn: function (){
+var self=this;
+var element;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+element=_st(_st(self._decorated())._asJQuery())._get_((0));
+$2=_st(_st(element)._scrollLeft()).__slash(_st(element)._scrollWidth());
+$ctx1.sendIdx["/"]=1;
+$1=_st($2).__at(_st(_st(element)._scrollTop()).__slash(_st(element)._scrollHeight()));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"scrollPercent",{element:element},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "scrollPercent\x0a\x09| element |\x0a\x09element := self decorated asJQuery get: 0.\x0a\x09^ (element scrollLeft / element scrollWidth) @ (element scrollTop / element scrollHeight)",
+messageSends: ["get:", "asJQuery", "decorated", "@", "/", "scrollLeft", "scrollWidth", "scrollTop", "scrollHeight"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollbarPosition",
+category: 'accessing',
+fn: function (){
+var self=this;
+var position;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+position=_st(self._scrollPercent()).__star(_st(self._domSize()).__minus(self._domScrollbarSize()));
+$2=_st(_st(position)._x())._rounded();
+$ctx1.sendIdx["rounded"]=1;
+$1=_st($2).__at(_st(_st(position)._y())._rounded());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"scrollbarPosition",{position:position},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "scrollbarPosition\x0a\x09| position |\x0a\x09position := self scrollPercent * (self domSize - self domScrollbarSize).\x0a\x09^ position x rounded @ position y rounded",
+messageSends: ["*", "scrollPercent", "-", "domSize", "domScrollbarSize", "@", "rounded", "x", "y"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "scrollbarSize",
@@ -252,6 +635,30 @@ referencedClasses: []
 }),
 smalltalk.MKScrollDecorator);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupEventHandlers",
+category: 'private',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.MKScrollDecorator.superclass.fn.prototype._setupEventHandlers.apply(_st(self), []);
+_st(_st(self["@root"])._asJQuery())._mousewheel_((function(event){
+return smalltalk.withContext(function($ctx2) {
+return _st(self._controller())._onMousewheel_(event);
+}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,1)})}));
+_st(_st(jQuery)._value_(window))._resize_((function(event){
+return smalltalk.withContext(function($ctx2) {
+return self._resized();
+}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,2)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"setupEventHandlers",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "setupEventHandlers\x0a\x09super setupEventHandlers.\x0a\x09\x0a\x09root asJQuery mousewheel: [ :event | \x0a\x09\x09self controller onMousewheel: event ].\x0a\x09\x09\x0a\x09(jQuery value: window) resize: [ :event | \x0a\x09\x09self resized ]",
+messageSends: ["setupEventHandlers", "mousewheel:", "asJQuery", "onMousewheel:", "controller", "resize:", "value:", "resized"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "setupScrollbars",
@@ -330,11 +737,52 @@ _st($5)._width_($6);
 _st(_st(self["@verticalScrollbar"])._asJQuery())._height_(_st(_st(height)._asString()).__comma("%"));
 return self}, function($ctx1) {$ctx1.fill(self,"updateScrollbars",{width:width,height:height},smalltalk.MKScrollDecorator)})},
 args: [],
-source: "updateScrollbars\x0a\x09| width height |\x0a\x09\x0a\x09width := self hasHorizontalOverflow\x0a\x09\x09ifTrue: [ self scrollbarSize x max: 10 ]\x0a\x09\x09ifFalse: [ 0 ].\x0a\x09height := self hasVerticalOverflow\x0a\x09\x09ifTrue: [ self scrollbarSize y max: 10 ]\x0a\x09\x09ifFalse: [ 0 ].\x0a\x09\x0a\x09horizontalScrollbar asJQuery width: width asString, '%'.\x0a\x09verticalScrollbar asJQuery height: height asString, '%'",
+source: "updateScrollbars\x0a\x09| width height |\x0a\x09\x0a\x09width := self hasHorizontalOverflow\x0a\x09\x09ifTrue: [ self scrollbarSize x max: 10 ]\x0a\x09\x09ifFalse: [ 0 ].\x0a\x09height := self hasVerticalOverflow\x0a\x09\x09ifTrue: [ self scrollbarSize y max: 10 ]\x0a\x09\x09ifFalse: [ 0 ].\x0a\x09\x0a\x09horizontalScrollbar asJQuery \x0a\x09\x09width: width asString, '%'.\x0a\x09verticalScrollbar asJQuery \x0a\x09\x09height: height asString, '%'",
 messageSends: ["ifTrue:ifFalse:", "hasHorizontalOverflow", "max:", "x", "scrollbarSize", "hasVerticalOverflow", "y", "width:", "asJQuery", ",", "asString", "height:"],
 referencedClasses: []
 }),
 smalltalk.MKScrollDecorator);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "updateScrollbarsPosition",
+category: 'updating',
+fn: function (){
+var self=this;
+var position;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+position=self._scrollbarPosition();
+$1=_st(self["@horizontalScrollbar"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+_st($1)._css_put_("left",_st(position)._x());
+$ctx1.sendIdx["css:put:"]=1;
+_st(_st(self["@verticalScrollbar"])._asJQuery())._css_put_("top",_st(position)._y());
+return self}, function($ctx1) {$ctx1.fill(self,"updateScrollbarsPosition",{position:position},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "updateScrollbarsPosition\x0a\x09| position |\x0a\x09position := self scrollbarPosition.\x0a\x09horizontalScrollbar asJQuery\x0a\x09\x09css: 'left' put: position x.\x0a\x09verticalScrollbar asJQuery\x0a\x09\x09css: 'top' put: position y",
+messageSends: ["scrollbarPosition", "css:put:", "asJQuery", "x", "y"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "verticalScrollbar",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@verticalScrollbar"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"verticalScrollbar",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "verticalScrollbar\x0a\x09^ verticalScrollbar",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
 
 });

File diff suppressed because it is too large
+ 136 - 136
js/Moka-Examples.js


+ 560 - 225
js/Moka-Views.js

@@ -431,7 +431,7 @@ smalltalk.MKSwitchView);
 
 
 
-smalltalk.addClass('MKContainerView', smalltalk.MKView, ['childView'], 'Moka-Views');
+smalltalk.addClass('MKContainerView', smalltalk.MKLayoutView, ['childView'], 'Moka-Views');
 smalltalk.MKContainerView.comment="I display my single `childView`. \x0a\x0aI am used to switch between views.";
 smalltalk.addMethod(
 smalltalk.method({
@@ -468,6 +468,24 @@ referencedClasses: []
 }),
 smalltalk.MKContainerView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "children",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=[self._childView()];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"children",{},smalltalk.MKContainerView)})},
+args: [],
+source: "children\x0a\x09^ { self childView }",
+messageSends: ["childView"],
+referencedClasses: []
+}),
+smalltalk.MKContainerView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "renderContentOn:",
@@ -691,7 +709,8 @@ smalltalk.MKHeadingView);
 
 
 
-smalltalk.addClass('MKOverlayView', smalltalk.MKView, ['childView'], 'Moka-Views');
+smalltalk.addClass('MKOverlayView', smalltalk.MKLayoutView, ['childView'], 'Moka-Views');
+smalltalk.MKOverlayView.comment="I display an transparent overlay, typically over other views, except my `childView`.\x0a\x0a## API\x0a\x0aCreate instances using the class-side `childView:` method.";
 smalltalk.addMethod(
 smalltalk.method({
 selector: "childView",
@@ -726,6 +745,24 @@ referencedClasses: []
 }),
 smalltalk.MKOverlayView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "children",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=[self._childView()];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"children",{},smalltalk.MKOverlayView)})},
+args: [],
+source: "children\x0a\x09^ { self childView }",
+messageSends: ["childView"],
+referencedClasses: []
+}),
+smalltalk.MKOverlayView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClass",
@@ -817,7 +854,7 @@ referencedClasses: []
 smalltalk.MKOverlayView.klass);
 
 
-smalltalk.addClass('MKPaneView', smalltalk.MKView, ['views'], 'Moka-Views');
+smalltalk.addClass('MKPaneView', smalltalk.MKLayoutView, ['views'], 'Moka-Views');
 smalltalk.MKPaneView.comment="I am a view containing other views.\x0a\x0a## API\x0a\x0aUse `#addView:` to add a view to the pane.";
 smalltalk.addMethod(
 smalltalk.method({
@@ -899,6 +936,24 @@ referencedClasses: []
 }),
 smalltalk.MKPaneView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "children",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self._views();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"children",{},smalltalk.MKPaneView)})},
+args: [],
+source: "children\x0a\x09^ self views",
+messageSends: ["views"],
+referencedClasses: []
+}),
+smalltalk.MKPaneView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "cssClass",
@@ -988,206 +1043,6 @@ smalltalk.MKPaneView);
 
 
 
-smalltalk.addClass('MKModalPaneView', smalltalk.MKPaneView, ['overlay', 'closeOnEnter', 'closeOnClick'], 'Moka-Views');
-smalltalk.addMethod(
-smalltalk.method({
-selector: "closeOnClick",
-category: 'accessing',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@closeOnClick"];
-if(($receiver = $2) == nil || $receiver == null){
-$1=false;
-} else {
-$1=$2;
-};
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"closeOnClick",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "closeOnClick\x0a\x09^ closeOnClick ifNil: [ false ]",
-messageSends: ["ifNil:"],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "closeOnClick:",
-category: 'accessing',
-fn: function (aBoolean){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-self["@closeOnClick"]=aBoolean;
-return self}, function($ctx1) {$ctx1.fill(self,"closeOnClick:",{aBoolean:aBoolean},smalltalk.MKModalPaneView)})},
-args: ["aBoolean"],
-source: "closeOnClick: aBoolean\x0a\x09closeOnClick := aBoolean",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "closeOnEnter",
-category: 'accessing',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@closeOnEnter"];
-if(($receiver = $2) == nil || $receiver == null){
-$1=false;
-} else {
-$1=$2;
-};
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"closeOnEnter",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "closeOnEnter\x0a\x09^ closeOnEnter ifNil: [ false ]",
-messageSends: ["ifNil:"],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "closeOnEnter:",
-category: 'accessing',
-fn: function (aBoolean){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-self["@closeOnEnter"]=aBoolean;
-return self}, function($ctx1) {$ctx1.fill(self,"closeOnEnter:",{aBoolean:aBoolean},smalltalk.MKModalPaneView)})},
-args: ["aBoolean"],
-source: "closeOnEnter: aBoolean\x0a\x09closeOnEnter := aBoolean",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "cssClass",
-category: 'accessing',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=_st(smalltalk.MKModalPaneView.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" mk_modal");
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "cssClass\x0a\x09^ super cssClass, ' mk_modal'",
-messageSends: [",", "cssClass"],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "defaultControllerClass",
-category: 'defaults',
-fn: function (){
-var self=this;
-function $MKModalPaneController(){return smalltalk.MKModalPaneController||(typeof MKModalPaneController=="undefined"?nil:MKModalPaneController)}
-return smalltalk.withContext(function($ctx1) { 
-return $MKModalPaneController();
-}, function($ctx1) {$ctx1.fill(self,"defaultControllerClass",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "defaultControllerClass\x0a\x09^ MKModalPaneController",
-messageSends: [],
-referencedClasses: ["MKModalPaneController"]
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "defaultLayout",
-category: 'defaults',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-var $2,$3,$1;
-$2=smalltalk.MKModalPaneView.superclass.fn.prototype._defaultLayout.apply(_st(self), []);
-_st($2)._centerY_((0));
-_st($2)._centerX_((0));
-_st($2)._width_((300));
-_st($2)._height_((200));
-$3=_st($2)._yourself();
-$1=$3;
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"defaultLayout",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "defaultLayout\x0a\x09^ super defaultLayout\x0a\x09\x09centerY: 0;\x0a\x09\x09centerX: 0;\x0a\x09\x09width: 300;\x0a\x09\x09height: 200;\x0a\x09\x09yourself",
-messageSends: ["centerY:", "defaultLayout", "centerX:", "width:", "height:", "yourself"],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "overlay",
-category: 'accessing',
-fn: function (){
-var self=this;
-function $MKOverlayView(){return smalltalk.MKOverlayView||(typeof MKOverlayView=="undefined"?nil:MKOverlayView)}
-return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@overlay"];
-if(($receiver = $2) == nil || $receiver == null){
-self["@overlay"]=_st($MKOverlayView())._childView_(self);
-$1=self["@overlay"];
-} else {
-$1=$2;
-};
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"overlay",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "overlay\x0a\x09^ overlay ifNil: [ overlay := MKOverlayView childView: self ]",
-messageSends: ["ifNil:", "childView:"],
-referencedClasses: ["MKOverlayView"]
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderOn:",
-category: 'rendering',
-fn: function (html){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-smalltalk.MKModalPaneView.superclass.fn.prototype._renderOn_.apply(_st(self), [html]);
-_st(self["@root"])._at_put_("tabindex","0");
-_st(_st(self["@root"])._asJQuery())._focus();
-_st(html)._with_(self._overlay());
-return self}, function($ctx1) {$ctx1.fill(self,"renderOn:",{html:html},smalltalk.MKModalPaneView)})},
-args: ["html"],
-source: "renderOn: html\x0a\x09super renderOn: html.\x0a\x09root at: 'tabindex' put: '0'.\x0a\x09root asJQuery focus.\x0a\x09html with: self overlay",
-messageSends: ["renderOn:", "at:put:", "focus", "asJQuery", "with:", "overlay"],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "zindex",
-category: 'accessing',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-return (1001);
-}, function($ctx1) {$ctx1.fill(self,"zindex",{},smalltalk.MKModalPaneView)})},
-args: [],
-source: "zindex\x0a\x09^ 1001",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.MKModalPaneView);
-
-
-
 smalltalk.addClass('MKPanelView', smalltalk.MKPaneView, [], 'Moka-Views');
 smalltalk.MKPanelView.comment="I am similar to a `MKPaneView` but I am scrollable and display a light background.";
 smalltalk.addMethod(
@@ -1455,25 +1310,20 @@ selector: "modalPaneView",
 category: 'views',
 fn: function (){
 var self=this;
-function $MKModalPaneView(){return smalltalk.MKModalPaneView||(typeof MKModalPaneView=="undefined"?nil:MKModalPaneView)}
+function $MKModalDecorator(){return smalltalk.MKModalDecorator||(typeof MKModalDecorator=="undefined"?nil:MKModalDecorator)}
+function $MKViewRemoved(){return smalltalk.MKViewRemoved||(typeof MKViewRemoved=="undefined"?nil:MKViewRemoved)}
 return smalltalk.withContext(function($ctx1) { 
-var $2,$3,$4,$6,$5,$7,$1;
+var $2,$3,$4,$1;
 $2=self["@modalPaneView"];
 if(($receiver = $2) == nil || $receiver == null){
-$3=_st($MKModalPaneView())._new();
+$3=_st($MKModalDecorator())._decorate_(self._listView());
 _st($3)._extraCssClass_("mk_dropdown_pane");
 _st($3)._closeOnEnter_(true);
 _st($3)._closeOnClick_(true);
-_st($3)._addView_(self._listView());
-$4=$3;
-$6=self._domPosition();
-$ctx1.sendIdx["domPosition"]=1;
-$5=_st($6)._x();
-_st($4)._left_($5);
-_st($3)._top_(_st(self._domPosition())._y());
-_st($3)._height_((400));
-$7=_st($3)._yourself();
-self["@modalPaneView"]=$7;
+$4=_st($3)._yourself();
+self["@modalPaneView"]=$4;
+self["@modalPaneView"];
+_st(self["@modalPaneView"])._on_send_to_($MKViewRemoved(),"focus",self);
 $1=self["@modalPaneView"];
 } else {
 $1=$2;
@@ -1481,9 +1331,9 @@ $1=$2;
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"modalPaneView",{},smalltalk.MKDropdownView)})},
 args: [],
-source: "modalPaneView\x0a\x09^ modalPaneView ifNil: [\x0a\x09\x09modalPaneView := MKModalPaneView new\x0a\x09\x09\x09extraCssClass: 'mk_dropdown_pane';\x0a\x09\x09\x09closeOnEnter: true;\x0a\x09\x09\x09closeOnClick: true;\x0a\x09\x09\x09addView: self listView;\x0a\x09\x09\x09left: self domPosition x;\x0a\x09\x09\x09top: self domPosition y;\x0a\x09\x09\x09\x22Max height of the list\x22\x0a\x09\x09\x09height: 400;\x0a\x09\x09\x09yourself ]",
-messageSends: ["ifNil:", "extraCssClass:", "new", "closeOnEnter:", "closeOnClick:", "addView:", "listView", "left:", "x", "domPosition", "top:", "y", "height:", "yourself"],
-referencedClasses: ["MKModalPaneView"]
+source: "modalPaneView\x0a\x09^ modalPaneView ifNil: [\x0a\x09\x09modalPaneView := (MKModalDecorator decorate: self listView)\x0a\x09\x09\x09extraCssClass: 'mk_dropdown_pane';\x0a\x09\x09\x09closeOnEnter: true;\x0a\x09\x09\x09closeOnClick: true;\x0a\x09\x09\x09yourself.\x0a\x09\x09modalPaneView \x0a\x09\x09\x09on: MKViewRemoved\x0a\x09\x09\x09send: #focus\x0a\x09\x09\x09to: self.\x0a\x09\x09modalPaneView ]",
+messageSends: ["ifNil:", "extraCssClass:", "decorate:", "listView", "closeOnEnter:", "closeOnClick:", "yourself", "on:send:to:"],
+referencedClasses: ["MKModalDecorator", "MKViewRemoved"]
 }),
 smalltalk.MKDropdownView);
 
@@ -1494,12 +1344,20 @@ category: 'actions',
 fn: function (){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-_st(self._modalPaneView())._render();
+var $1,$2,$4,$3,$5;
+$1=self._modalPaneView();
+$2=$1;
+$4=self._domPosition();
+$ctx1.sendIdx["domPosition"]=1;
+$3=_st($4)._x();
+_st($2)._left_($3);
+_st($1)._top_(_st(self._domPosition())._y());
+$5=_st($1)._render();
 _st(self._listView())._focus();
 return self}, function($ctx1) {$ctx1.fill(self,"popupList",{},smalltalk.MKDropdownView)})},
 args: [],
-source: "popupList\x0a\x09\x22Show a new list view inside a modal pane\x22\x0a\x09self modalPaneView render.\x0a\x09self listView focus",
-messageSends: ["render", "modalPaneView", "focus", "listView"],
+source: "popupList\x0a\x09\x22Show a new list view inside a modal pane\x22\x0a\x09self modalPaneView \x0a\x09\x09left: self domPosition x;\x0a\x09\x09top: self domPosition y;\x0a\x09\x09render.\x0a\x09self listView focus",
+messageSends: ["left:", "modalPaneView", "x", "domPosition", "top:", "y", "render", "focus", "listView"],
 referencedClasses: []
 }),
 smalltalk.MKDropdownView);
@@ -1744,6 +1602,7 @@ category: 'private',
 fn: function (aListItem){
 var self=this;
 var parent,position;
+function $MKViewScroll(){return smalltalk.MKViewScroll||(typeof MKViewScroll=="undefined"?nil:MKViewScroll)}
 return smalltalk.withContext(function($ctx1) { 
 var $1,$4,$3,$2,$5,$9,$8,$11,$10,$7,$6,$15,$14,$16,$13,$17,$12,$18,$22,$23,$21,$20,$19;
 $1=_st(aListItem)._get_((0));
@@ -1802,11 +1661,12 @@ $19=_st($20).__plus((10));
 $ctx1.sendIdx["+"]=3;
 _st($18)._scrollTop_($19);
 };
+self._announce_(_st($MKViewScroll())._view_(self));
 return self}, function($ctx1) {$ctx1.fill(self,"ensureVisible:",{aListItem:aListItem,parent:parent,position:position},smalltalk.MKListView)})},
 args: ["aListItem"],
-source: "ensureVisible: aListItem\x09\x0a\x09\x22Move the scrollbar to show the active element\x22\x0a\x09\x0a\x09| parent position |\x0a\x09(aListItem get: 0) ifNil: [ ^ self ].\x0a\x09position := self positionOf: aListItem.\x0a\x09parent := aListItem parent.\x0a\x09\x0a    aListItem position top < 0 ifTrue: [\x0a\x09\x09(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].\x0a    aListItem position top + aListItem height > parent height ifTrue: [ \x0a\x09\x09(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ]",
-messageSends: ["ifNil:", "get:", "positionOf:", "parent", "ifTrue:", "<", "top", "position", "scrollTop:", "-", "+", "scrollTop", ">", "height"],
-referencedClasses: []
+source: "ensureVisible: aListItem\x09\x0a\x09\x22Move the scrollbar to show the active element\x22\x0a\x09\x0a\x09| parent position |\x0a\x09(aListItem get: 0) ifNil: [ ^ self ].\x0a\x09position := self positionOf: aListItem.\x0a\x09parent := aListItem parent.\x0a\x09\x0a    aListItem position top < 0 ifTrue: [\x0a\x09\x09(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].\x0a    aListItem position top + aListItem height > parent height ifTrue: [ \x0a\x09\x09(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ].\x0a\x09\x0a\x09self announce: (MKViewScroll view: self)",
+messageSends: ["ifNil:", "get:", "positionOf:", "parent", "ifTrue:", "<", "top", "position", "scrollTop:", "-", "+", "scrollTop", ">", "height", "announce:", "view:"],
+referencedClasses: ["MKViewScroll"]
 }),
 smalltalk.MKListView);
 
@@ -2073,6 +1933,481 @@ smalltalk.MKSourceListView);
 
 
 
+smalltalk.addClass('MKSplitView', smalltalk.MKLayoutView, ['firstView', 'secondView', 'splitter'], 'Moka-Views');
+smalltalk.MKSplitView.comment="I am the superclass of all split views. I arrange two child view with a splitter between them.\x0a\x0a## API\x0a\x0aCreate instances using the class-side method `firstView:secondView:`.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "children",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=[self._firstView(),self._secondView()];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"children",{},smalltalk.MKSplitView)})},
+args: [],
+source: "children\x0a\x09^ { self firstView. self secondView }",
+messageSends: ["firstView", "secondView"],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(smalltalk.MKSplitView.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" mk_split_view");
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKSplitView)})},
+args: [],
+source: "cssClass\x0a\x09^ super cssClass, ' mk_split_view'",
+messageSends: [",", "cssClass"],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "defaultThickness",
+category: 'defaults',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return (300);
+}, function($ctx1) {$ctx1.fill(self,"defaultThickness",{},smalltalk.MKSplitView)})},
+args: [],
+source: "defaultThickness\x0a\x09^ 300",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "firstView",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@firstView"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"firstView",{},smalltalk.MKSplitView)})},
+args: [],
+source: "firstView\x0a\x09^ firstView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "firstView:",
+category: 'accessing',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@firstView"]=aView;
+return self}, function($ctx1) {$ctx1.fill(self,"firstView:",{aView:aView},smalltalk.MKSplitView)})},
+args: ["aView"],
+source: "firstView: aView\x0a\x09firstView := aView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(html)._with_(self._firstView());
+$ctx1.sendIdx["with:"]=1;
+self["@splitter"]=_st(_st(html)._div())._class_(self._splitterCssClass());
+_st(html)._with_(self._secondView());
+self._setupSplitter();
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.MKSplitView)})},
+args: ["html"],
+source: "renderContentOn: html\x0a\x09html with: self firstView.\x0a\x09splitter := html div class: self splitterCssClass.\x0a\x09html with: self secondView.\x0a        \x0a\x09self setupSplitter",
+messageSends: ["with:", "firstView", "class:", "div", "splitterCssClass", "secondView", "setupSplitter"],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "secondView",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@secondView"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"secondView",{},smalltalk.MKSplitView)})},
+args: [],
+source: "secondView\x0a\x09^ secondView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "secondView:",
+category: 'accessing',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@secondView"]=aView;
+return self}, function($ctx1) {$ctx1.fill(self,"secondView:",{aView:aView},smalltalk.MKSplitView)})},
+args: ["aView"],
+source: "secondView: aView\x0a\x09secondView := aView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupSplitter",
+category: 'private',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self._subclassResponsibility();
+return self}, function($ctx1) {$ctx1.fill(self,"setupSplitter",{},smalltalk.MKSplitView)})},
+args: [],
+source: "setupSplitter\x0a\x09self subclassResponsibility",
+messageSends: ["subclassResponsibility"],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "splitterCssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return "mk_splitter";
+}, function($ctx1) {$ctx1.fill(self,"splitterCssClass",{},smalltalk.MKSplitView)})},
+args: [],
+source: "splitterCssClass\x0a\x09^ 'mk_splitter'",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKSplitView);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "firstView:secondView:",
+category: 'instance creation',
+fn: function (aView,anotherView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._firstView_(aView);
+_st($2)._secondView_(anotherView);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"firstView:secondView:",{aView:aView,anotherView:anotherView},smalltalk.MKSplitView.klass)})},
+args: ["aView", "anotherView"],
+source: "firstView: aView secondView: anotherView\x0a\x09^ self new\x0a\x09\x09firstView: aView;\x0a\x09\x09secondView: anotherView;\x0a\x09\x09yourself",
+messageSends: ["firstView:", "new", "secondView:", "yourself"],
+referencedClasses: []
+}),
+smalltalk.MKSplitView.klass);
+
+
+smalltalk.addClass('MKHorizontalSplitView', smalltalk.MKSplitView, ['leftThickness', 'rightThickness'], 'Moka-Views');
+smalltalk.MKHorizontalSplitView.comment="I split my child views vertically.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(smalltalk.MKHorizontalSplitView.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" horizontal");
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKHorizontalSplitView)})},
+args: [],
+source: "cssClass\x0a\x09^ super cssClass, ' horizontal'",
+messageSends: [",", "cssClass"],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "leftThickness",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$1;
+$2=self["@leftThickness"];
+if(($receiver = $2) == nil || $receiver == null){
+$1=self._defaultThickness();
+} else {
+$1=$2;
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"leftThickness",{},smalltalk.MKHorizontalSplitView)})},
+args: [],
+source: "leftThickness\x0a\x09^ leftThickness ifNil: [ self defaultThickness ]",
+messageSends: ["ifNil:", "defaultThickness"],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "leftThickness:",
+category: 'accessing',
+fn: function (aNumber){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@rightThickness"]=nil;
+self["@leftThickness"]=aNumber;
+return self}, function($ctx1) {$ctx1.fill(self,"leftThickness:",{aNumber:aNumber},smalltalk.MKHorizontalSplitView)})},
+args: ["aNumber"],
+source: "leftThickness: aNumber\x0a\x09rightThickness := nil.\x0a\x09leftThickness := aNumber",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self._leftThickness();
+$ctx1.sendIdx["leftThickness"]=1;
+if(($receiver = $1) == nil || $receiver == null){
+$1;
+} else {
+var thickness;
+thickness=$receiver;
+_st(self._firstView())._width_(thickness);
+_st(self._secondView())._left_(thickness);
+};
+smalltalk.MKHorizontalSplitView.superclass.fn.prototype._renderContentOn_.apply(_st(self), [html]);
+_st(_st(self["@splitter"])._asJQuery())._css_put_("left",self._leftThickness());
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.MKHorizontalSplitView)})},
+args: ["html"],
+source: "renderContentOn: html\x0a\x09self leftThickness ifNotNil: [ :thickness |\x0a\x09\x09self firstView width: thickness.\x0a\x09\x09self secondView left: thickness ].\x0a\x09\x09\x0a\x09super renderContentOn: html.\x0a\x09splitter asJQuery css: 'left' put: self leftThickness",
+messageSends: ["ifNotNil:", "leftThickness", "width:", "firstView", "left:", "secondView", "renderContentOn:", "css:put:", "asJQuery"],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "resize:",
+category: 'actions',
+fn: function (aNumber){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=_st(self._firstView())._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$2=_st(aNumber)._asMokaCssString();
+$ctx1.sendIdx["asMokaCssString"]=1;
+_st($1)._css_put_("width",$2);
+$ctx1.sendIdx["css:put:"]=1;
+$3=_st(self["@splitter"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=2;
+$4=_st(aNumber)._asMokaCssString();
+$ctx1.sendIdx["asMokaCssString"]=2;
+_st($3)._css_put_("left",$4);
+$ctx1.sendIdx["css:put:"]=2;
+_st(_st(self._secondView())._asJQuery())._css_put_("left",_st(aNumber)._asMokaCssString());
+return self}, function($ctx1) {$ctx1.fill(self,"resize:",{aNumber:aNumber},smalltalk.MKHorizontalSplitView)})},
+args: ["aNumber"],
+source: "resize: aNumber\x0a    self firstView asJQuery css: 'width' put: aNumber asMokaCssString.\x0a\x09splitter asJQuery css: 'left' put: aNumber asMokaCssString.\x0a\x09self secondView asJQuery css: 'left' put: aNumber asMokaCssString",
+messageSends: ["css:put:", "asJQuery", "firstView", "asMokaCssString", "secondView"],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "rightThickness",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@rightThickness"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"rightThickness",{},smalltalk.MKHorizontalSplitView)})},
+args: [],
+source: "rightThickness\x0a\x09^ rightThickness",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "rightThickness:",
+category: 'accessing',
+fn: function (aNumber){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@leftThickness"]=nil;
+self["@rightThickness"]=aNumber;
+return self}, function($ctx1) {$ctx1.fill(self,"rightThickness:",{aNumber:aNumber},smalltalk.MKHorizontalSplitView)})},
+args: ["aNumber"],
+source: "rightThickness: aNumber\x0a\x09leftThickness := nil.\x0a\x09rightThickness := aNumber",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupSplitter",
+category: 'private',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$3,$4,$5,$6,$7,$2;
+$1=_st(self["@splitter"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$3="axis".__minus_gt("x");
+$ctx1.sendIdx["->"]=1;
+$4="containment".__minus_gt(_st(_st(self["@splitter"])._asJQuery())._parent());
+$ctx1.sendIdx["->"]=2;
+$5="helper".__minus_gt("clone");
+$ctx1.sendIdx["->"]=3;
+$6="cursor".__minus_gt("ew-resize");
+$ctx1.sendIdx["->"]=4;
+$7="stop".__minus_gt((function(){
+return smalltalk.withContext(function($ctx2) {
+return self._resized();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
+$ctx1.sendIdx["->"]=5;
+$2=smalltalk.HashedCollection._from_([$3,$4,$5,$6,$7,"drag".__minus_gt((function(e,ui){
+return smalltalk.withContext(function($ctx2) {
+return self._resize_(_st(_st(ui)._offset())._left());
+}, function($ctx2) {$ctx2.fillBlock({e:e,ui:ui},$ctx1,2)})}))]);
+_st($1)._draggable_($2);
+return self}, function($ctx1) {$ctx1.fill(self,"setupSplitter",{},smalltalk.MKHorizontalSplitView)})},
+args: [],
+source: "setupSplitter\x0a\x09splitter asJQuery draggable: #{ \x0a    \x09'axis' -> 'x'. \x0a        'containment' -> splitter asJQuery parent.\x0a        'helper' -> 'clone'.\x0a\x09\x09'cursor' -> 'ew-resize'.\x0a\x09\x09'stop' -> [ self resized ].\x0a        'drag' -> [ :e :ui | self resize: ui offset left ] }",
+messageSends: ["draggable:", "asJQuery", "->", "parent", "resized", "resize:", "left", "offset"],
+referencedClasses: []
+}),
+smalltalk.MKHorizontalSplitView);
+
+
+
+smalltalk.addClass('MKVerticalSplitView', smalltalk.MKSplitView, [], 'Moka-Views');
+smalltalk.MKVerticalSplitView.comment="I split my child views horizontally.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(smalltalk.MKVerticalSplitView.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" vertical");
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKVerticalSplitView)})},
+args: [],
+source: "cssClass\x0a\x09^ super cssClass, ' vertical'",
+messageSends: [",", "cssClass"],
+referencedClasses: []
+}),
+smalltalk.MKVerticalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "resize:",
+category: 'actions',
+fn: function (aNumber){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$4;
+$1=_st(self._firstView())._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$2=_st(aNumber)._asMokaCssString();
+$ctx1.sendIdx["asMokaCssString"]=1;
+_st($1)._css_put_("right",$2);
+$ctx1.sendIdx["css:put:"]=1;
+$3=_st(self["@splitter"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=2;
+$4=_st(aNumber)._asMokaCssString();
+$ctx1.sendIdx["asMokaCssString"]=2;
+_st($3)._css_put_("left",$4);
+$ctx1.sendIdx["css:put:"]=2;
+_st(_st(self._secondView())._asJQuery())._css_put_("left",_st(aNumber)._asMokaCssString());
+return self}, function($ctx1) {$ctx1.fill(self,"resize:",{aNumber:aNumber},smalltalk.MKVerticalSplitView)})},
+args: ["aNumber"],
+source: "resize: aNumber\x0a    self firstView asJQuery css: 'right' put: aNumber asMokaCssString.\x0a\x09splitter asJQuery css: 'left' put: aNumber asMokaCssString.\x0a\x09self secondView asJQuery css: 'left' put: aNumber asMokaCssString",
+messageSends: ["css:put:", "asJQuery", "firstView", "asMokaCssString", "secondView"],
+referencedClasses: []
+}),
+smalltalk.MKVerticalSplitView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupSplitter",
+category: 'private',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$3,$4,$5,$6,$7,$2;
+$1=_st(self["@splitter"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$3="axis".__minus_gt("x");
+$ctx1.sendIdx["->"]=1;
+$4="containment".__minus_gt(_st(_st(self["@splitter"])._asJQuery())._parent());
+$ctx1.sendIdx["->"]=2;
+$5="helper".__minus_gt("clone");
+$ctx1.sendIdx["->"]=3;
+$6="cursor".__minus_gt("ns-resize");
+$ctx1.sendIdx["->"]=4;
+$7="stop".__minus_gt((function(){
+return smalltalk.withContext(function($ctx2) {
+return self._resized();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
+$ctx1.sendIdx["->"]=5;
+$2=smalltalk.HashedCollection._from_([$3,$4,$5,$6,$7,"drag".__minus_gt((function(e,ui){
+return smalltalk.withContext(function($ctx2) {
+return self._resize_(_st(_st(ui)._offset())._left());
+}, function($ctx2) {$ctx2.fillBlock({e:e,ui:ui},$ctx1,2)})}))]);
+_st($1)._draggable_($2);
+return self}, function($ctx1) {$ctx1.fill(self,"setupSplitter",{},smalltalk.MKVerticalSplitView)})},
+args: [],
+source: "setupSplitter\x0a\x09splitter asJQuery draggable: #{ \x0a    \x09'axis' -> 'x'. \x0a        'containment' -> splitter asJQuery parent.\x0a        'helper' -> 'clone'.\x0a\x09\x09'cursor' -> 'ns-resize'.\x0a\x09\x09'stop' -> [ self resized ].\x0a        'drag' -> [ :e :ui | self resize: (ui offset left) ] }",
+messageSends: ["draggable:", "asJQuery", "->", "parent", "resized", "resize:", "left", "offset"],
+referencedClasses: []
+}),
+smalltalk.MKVerticalSplitView);
+
+
+
 smalltalk.addClass('MKTextAreaView', smalltalk.MKSingleAspectView, [], 'Moka-Views');
 smalltalk.MKTextAreaView.comment="I am an text area view. My default controller is `MKAnyKeyInputController`.\x0a\x0aMy controller must answer to `#onKeyPressed:`.";
 smalltalk.addMethod(

+ 77 - 0
st/Moka-Announcements.st

@@ -0,0 +1,77 @@
+Smalltalk current createPackage: 'Moka-Announcements'!
+Object subclass: #MKAnnouncement
+	instanceVariableNames: ''
+	package: 'Moka-Announcements'!
+!MKAnnouncement commentStamp!
+I am the root class of all announcements sent in Moka.!
+
+MKAnnouncement subclass: #MKAspectChanged
+	instanceVariableNames: 'aspect'
+	package: 'Moka-Announcements'!
+!MKAspectChanged commentStamp!
+I am announced whenever an `aspect` is changed.
+
+## API
+
+Create instances using the class-side method `#aspect:`!
+
+!MKAspectChanged methodsFor: 'accessing'!
+
+aspect
+	^ aspect
+!
+
+aspect: aSelector
+	aspect := aSelector
+! !
+
+!MKAspectChanged class methodsFor: 'instance creation'!
+
+aspect: aSelector
+	^ self new
+		aspect: aSelector;
+		yourself
+! !
+
+MKAnnouncement subclass: #MKViewAnnouncement
+	instanceVariableNames: 'view'
+	package: 'Moka-Announcements'!
+!MKViewAnnouncement commentStamp!
+I am the root class of all viewn announcements. I hold a `view` object.
+
+## API
+
+Create instance with the class-side method `#view:`!
+
+!MKViewAnnouncement methodsFor: 'accessing'!
+
+view
+	^ view
+!
+
+view: aView
+	view := aView
+! !
+
+!MKViewAnnouncement class methodsFor: 'instance creation'!
+
+view: aView
+	^ self new
+		view: aView;
+		yourself
+! !
+
+MKViewAnnouncement subclass: #MKViewRemoved
+	instanceVariableNames: ''
+	package: 'Moka-Announcements'!
+!MKViewRemoved commentStamp!
+I am announced when a view gets removed from the DOM.!
+
+MKViewAnnouncement subclass: #MKViewScroll
+	instanceVariableNames: ''
+	package: 'Moka-Announcements'!
+!MKViewScroll commentStamp!
+I am often used in conjunction with `MKScrollDecorator`.
+
+I am announced when a view's scroll changed programatically!
+

+ 30 - 18
st/Moka-Controllers.st

@@ -87,6 +87,8 @@ toggle
 MKAspectsController subclass: #MKDropdownController
 	instanceVariableNames: ''
 	package: 'Moka-Controllers'!
+!MKDropdownController commentStamp!
+I am the default controller for `MKDropdownView`.!
 
 !MKDropdownController methodsFor: 'actions'!
 
@@ -204,24 +206,27 @@ onMouseMove: anEvent
 	self activateItem: (self itemForTarget: anEvent target)
 ! !
 
-MKSingleAspectController subclass: #MKModalPaneController
+MKSingleAspectController subclass: #MKModalController
 	instanceVariableNames: ''
 	package: 'Moka-Controllers'!
 
-!MKModalPaneController methodsFor: 'actions'!
+!MKModalController methodsFor: 'actions'!
 
 onClick: anEvent
 	self view closeOnClick ifTrue: [ self removeView ]
 !
 
 onKeyDown: anEvent
+	self view closeOnEnter ifTrue: [
+		anEvent keyCode = String cr asciiValue ifTrue: [
+			self removeView.
+			anEvent 
+				stopPropagation;
+				preventDefault ] ].
+	
 	"ESC"
 	anEvent keyCode = 27 ifTrue: [
-		self removeView ].
-		
-	self view closeOnEnter ifTrue: [
-		anEvent keyCode = String cr asciiValue ifTrue: [ 
-			self removeView ] ]
+		self removeView ]
 !
 
 removeView
@@ -288,25 +293,32 @@ isRepeating
 MKController subclass: #MKScrollController
 	instanceVariableNames: ''
 	package: 'Moka-Controllers'!
+!MKScrollController commentStamp!
+I am the default controller for `MKScrollDecorator`.!
 
-!MKScrollController methodsFor: 'accessing'!
+!MKScrollController methodsFor: 'actions'!
 
-position
-	^ self view domDecoratedSize * self scrollPercent
+onDecoratedScroll
+	self view updateScrollbarsPosition
 !
 
-scrollPercent
-	^ self view domScrollPosition / (self view domSize - self view domScrollbarSize)
-! !
+onHorizontalDrag: anEvent
+	(self view decorated asJQuery get: 0) at: 'scrollLeft' put: self view domScrollPosition x
+!
 
-!MKScrollController methodsFor: 'actions'!
+onMousewheel: anEvent
+	anEvent deltaY ~= 0 ifTrue: [
+		self view scrollDeltaY: anEvent deltaY * 10 ].
+	
+	anEvent deltaX ~= 0 ifTrue: [
+		self view scrollDeltaX: anEvent deltaX * 10 ]
+!
 
-onHorizontalDrag: anEvent
-	(self view decorated asJQuery get: 0) at: 'scrollLeft' put: self position x
+onResize
+	self view resized
 !
 
 onVerticalDrag: anEvent
-	console log: self position asString.
-	(self view decorated asJQuery get: 0) at: 'scrollTop' put: self position y
+	(self view decorated asJQuery get: 0) at: 'scrollTop' put: self view domScrollPosition y
 ! !
 

+ 166 - 114
st/Moka-Core.st

@@ -103,26 +103,28 @@ performAspectActionWith: anObject
 		with: anObject
 ! !
 
-Object subclass: #MKModel
+Object subclass: #MKObservable
 	instanceVariableNames: 'announcer'
 	package: 'Moka-Core'!
-!MKModel commentStamp!
-I implement the Model part of the MVC pattern in Moka.
+!MKObservable commentStamp!
+View models are typically subclasses of me. 
 
-I am the abstract superclass of all Moka model. The observer pattern is implemented through an `announcer` object.
+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 model changes
+  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.!
 
-!MKModel methodsFor: 'announcements'!
+!MKObservable methodsFor: 'announcements'!
 
 announce: anAnnouncement
 	announcer announce: anAnnouncement
@@ -131,7 +133,7 @@ announce: anAnnouncement
 changed: aSelector
 	"Trigger `#update:` to all listening aspect views"
 	
-	self announce: (MKModelChanged aspect: aSelector)
+	self announce: (MKAspectChanged aspect: aSelector)
 !
 
 on: anAnnouncement do: aBlock
@@ -142,39 +144,15 @@ on: anAnnouncement send: aSelector to: anObject
 	announcer on: anAnnouncement send: aSelector to: anObject
 ! !
 
-!MKModel methodsFor: 'initialization'!
+!MKObservable methodsFor: 'initialization'!
 
 initialize
 	super initialize.
 	announcer := Announcer new
 ! !
 
-Object subclass: #MKModelChanged
-	instanceVariableNames: 'aspect'
-	package: 'Moka-Core'!
-!MKModelChanged commentStamp!
-I am an announcement announced when a model is changed.!
-
-!MKModelChanged methodsFor: 'accessing'!
-
-aspect
-	^ aspect
-!
-
-aspect: aSelector
-	aspect := aSelector
-! !
-
-!MKModelChanged class methodsFor: 'instance creation'!
-
-aspect: aSelector
-	^ self new
-		aspect: aSelector;
-		yourself
-! !
-
-Widget subclass: #MKView
-	instanceVariableNames: 'controller model root layout extraCssClass'
+MKObservable subclass: #MKView
+	instanceVariableNames: 'controller model root extraCssClass'
 	package: 'Moka-Core'!
 !MKView commentStamp!
 I implement the View part of the MVC pattern in Moka.
@@ -189,6 +167,11 @@ I implement the View part of the MVC pattern in Moka.
 
 !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`
@@ -215,6 +198,10 @@ cssClass
 			stream << ' ' << self extraCssClass ] ]
 !
 
+cssStyle
+	^ ''
+!
+
 extraCssClass
 	^ extraCssClass ifNil: [ '' ]
 !
@@ -223,10 +210,6 @@ extraCssClass: aString
 	extraCssClass := aString
 !
 
-layout
-	^ layout ifNil: [ layout := self defaultLayout ]
-!
-
 model
 	^ model
 !
@@ -252,7 +235,25 @@ focus
 
 remove
 	"Removes the receiver from the DOM"
-	root ifNotNil: [ root asJQuery remove ]
+	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'!
@@ -265,15 +266,6 @@ asJQuery
 
 defaultControllerClass
 	^ MKController
-!
-
-defaultLayout
-	^ MKLayout new
-		left: 0;
-		top: 0;
-		right: 0;
-		bottom: 0;
-		yourself
 ! !
 
 !MKView methodsFor: 'dom'!
@@ -296,7 +288,116 @@ defaultController
 	^ self defaultControllerClass new
 ! !
 
-!MKView methodsFor: 'layout'!
+!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
@@ -362,67 +463,7 @@ width: aNumber
 	self layout width: aNumber
 ! !
 
-!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 layout asCssString;
-		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 error: 'The view has not been rendered yet' ].
-	
-	root asJQuery empty.
-	[ :html | self renderContentOn: html ] 
-		appendToJQuery: root asJQuery
-! !
-
-!MKView class methodsFor: 'instance creation'!
+!MKLayoutView class methodsFor: 'instance creation'!
 
 model: aModel
 	^ self new
@@ -436,7 +477,7 @@ model: aModel controller: aController
 		yourself
 ! !
 
-MKView subclass: #MKAspectsView
+MKLayoutView subclass: #MKAspectsView
 	instanceVariableNames: ''
 	package: 'Moka-Core'!
 !MKAspectsView commentStamp!
@@ -460,7 +501,7 @@ observeModel
 	super observeModel.
 	
 	self model
-		on: MKModelChanged
+		on: MKAspectChanged
 		send: 'update:'
 		to: self
 ! !
@@ -516,7 +557,7 @@ model: aModel aspect: aSelector
 		yourself
 ! !
 
-MKView subclass: #MKDecorator
+MKLayoutView subclass: #MKDecorator
 	instanceVariableNames: 'decorated'
 	package: 'Moka-Core'!
 !MKDecorator commentStamp!
@@ -530,12 +571,23 @@ To decorate a view, use the class-side `#decorate:` method.!
 
 !MKDecorator methodsFor: 'accessing'!
 
+children
+	^ { self decorated }
+!
+
 decorated
 	^ decorated
 !
 
 decorated: aView
-	decorated := aView
+	decorated := aView.
+	self observeDecorated
+! !
+
+!MKDecorator methodsFor: 'observing'!
+
+observeDecorated
+	"Override in subclasses"
 ! !
 
 !MKDecorator methodsFor: 'rendering'!

+ 149 - 2
st/Moka-Decorators.st

@@ -1,7 +1,71 @@
 Smalltalk current createPackage: 'Moka-Decorators'!
+MKDecorator subclass: #MKModalDecorator
+	instanceVariableNames: 'overlay closeOnEnter closeOnClick'
+	package: 'Moka-Decorators'!
+!MKModalDecorator commentStamp!
+I render my `decorated` view as a modal pane.!
+
+!MKModalDecorator methodsFor: 'accessing'!
+
+closeOnClick
+	^ closeOnClick ifNil: [ false ]
+!
+
+closeOnClick: aBoolean
+	closeOnClick := aBoolean
+!
+
+closeOnEnter
+	^ closeOnEnter ifNil: [ false ]
+!
+
+closeOnEnter: aBoolean
+	closeOnEnter := aBoolean
+!
+
+cssClass
+	^ super cssClass, ' mk_modal'
+!
+
+overlay
+	^ overlay ifNil: [ overlay := MKOverlayView childView: self ]
+!
+
+zindex
+	^ 1001
+! !
+
+!MKModalDecorator methodsFor: 'defaults'!
+
+defaultControllerClass
+	^ MKModalController
+!
+
+defaultLayout
+	^ super defaultLayout
+		centerY: 0;
+		centerX: 0;"
+		width: 300;
+		height: 200;"
+		yourself
+! !
+
+!MKModalDecorator methodsFor: 'rendering'!
+
+renderOn: html
+	super renderOn: html.
+	root at: 'tabindex' put: '0'.
+	root asJQuery focus.
+	html with: self overlay
+! !
+
 MKDecorator subclass: #MKScrollDecorator
 	instanceVariableNames: 'verticalScrollbar horizontalScrollbar'
 	package: 'Moka-Decorators'!
+!MKScrollDecorator commentStamp!
+I decorate a view adding scrollbars around it.
+
+The `decorated` view can send `MKViewScrolled` announcement to update the scrollbars position.!
 
 !MKScrollDecorator methodsFor: 'accessing'!
 
@@ -9,12 +73,57 @@ cssClass
 	^ super cssClass, ' mk_scroll'
 !
 
+horizontalScrollbar
+	^ horizontalScrollbar
+!
+
+scrollPercent
+	| element |
+	element := self decorated asJQuery get: 0.
+	^ (element scrollLeft / element scrollWidth) @ (element scrollTop / element scrollHeight)
+!
+
+scrollbarPosition
+	| position |
+	position := self scrollPercent * (self domSize - self domScrollbarSize).
+	^ position x rounded @ position y rounded
+!
+
 scrollbarSize
 	| domSize overflow |
 	
 	domSize := self domSize.
 	overflow := self domOverflow.
 	^ ((domSize x / (overflow x + domSize x)) * 100) @ ((domSize y / (overflow y + domSize y) * 100))
+!
+
+verticalScrollbar
+	^ verticalScrollbar
+! !
+
+!MKScrollDecorator methodsFor: 'actions'!
+
+resized
+	super resized.
+	self updateScrollbars
+!
+
+scrollDeltaX: aNumber
+	| scrollbar left maxLeft |
+	scrollbar := self horizontalScrollbar asJQuery.
+	maxLeft := self domSize x - scrollbar width.
+	left := ((scrollbar position left + aNumber) max: 0) min: maxLeft.
+	scrollbar css: 'left' put: left.
+	(self decorated asJQuery get: 0) at: 'scrollLeft' put: self domScrollPosition x
+!
+
+scrollDeltaY: aNumber
+	| scrollbar top maxTop |
+	scrollbar := self verticalScrollbar asJQuery.
+	maxTop := self domSize y - scrollbar height.
+	top := ((scrollbar position top - aNumber) max: 0) min: maxTop.
+	scrollbar css: 'top' put: top.
+	(self decorated asJQuery get: 0) at: 'scrollTop' put: self domScrollPosition y
 ! !
 
 !MKScrollDecorator methodsFor: 'defaults'!
@@ -37,7 +146,15 @@ domOverflow
 	^ (element scrollWidth - element clientWidth) @ (element scrollHeight - element clientHeight)
 !
 
+domScrollPercent
+	^ self domScrollbarPosition / (self domSize - self domScrollbarSize)
+!
+
 domScrollPosition
+	^ (self domDecoratedSize - self domSize) * self domScrollPercent
+!
+
+domScrollbarPosition
 	^ horizontalScrollbar asJQuery position left @ verticalScrollbar asJQuery position top
 !
 
@@ -45,8 +162,27 @@ domScrollbarSize
 	^ horizontalScrollbar asJQuery width @ verticalScrollbar asJQuery height
 ! !
 
+!MKScrollDecorator methodsFor: 'observing'!
+
+observeDecorated
+	self decorated 
+		on: MKViewScroll 
+		send: #onDecoratedScroll
+		to: self controller
+! !
+
 !MKScrollDecorator methodsFor: 'private'!
 
+setupEventHandlers
+	super setupEventHandlers.
+	
+	root asJQuery mousewheel: [ :event | 
+		self controller onMousewheel: event ].
+		
+	(jQuery value: window) resize: [ :event | 
+		self resized ]
+!
+
 setupScrollbars
 	verticalScrollbar asJQuery draggable: #{
 		'containment' -> 'parent'.
@@ -107,7 +243,18 @@ updateScrollbars
 		ifTrue: [ self scrollbarSize y max: 10 ]
 		ifFalse: [ 0 ].
 	
-	horizontalScrollbar asJQuery width: width asString, '%'.
-	verticalScrollbar asJQuery height: height asString, '%'
+	horizontalScrollbar asJQuery 
+		width: width asString, '%'.
+	verticalScrollbar asJQuery 
+		height: height asString, '%'
+!
+
+updateScrollbarsPosition
+	| position |
+	position := self scrollbarPosition.
+	horizontalScrollbar asJQuery
+		css: 'left' put: position x.
+	verticalScrollbar asJQuery
+		css: 'top' put: position y
 ! !
 

+ 9 - 9
st/Moka-Examples.st

@@ -1,5 +1,5 @@
 Smalltalk current createPackage: 'Moka-Examples'!
-MKModel subclass: #MKClassesListBuilder
+MKObservable subclass: #MKClassesListBuilder
 	instanceVariableNames: ''
 	package: 'Moka-Examples'!
 
@@ -37,7 +37,7 @@ initialize
 	self new build
 ! !
 
-MKModel subclass: #MKClassesModel
+MKObservable subclass: #MKClassesModel
 	instanceVariableNames: 'classes selectedClass'
 	package: 'Moka-Examples'!
 
@@ -65,7 +65,6 @@ Object subclass: #MKCounterBuilder
 build
 	| pane |
 	pane := MKPanelView new
-		borderRight: 1;
 		yourself.
 	
 	pane addView: ((MKHeadingView model: self counter aspect: #count)
@@ -141,11 +140,12 @@ build
 		top: 440;
 		yourself).
 	
-	(MKScrollDecorator decorate: pane)
-		top: 200;
-		width: 400;
-		bottom: 0;
-		render
+	(MKHorizontalSplitView 
+		firstView: (MKScrollDecorator decorate: pane)
+		secondView: MKLayoutView new) 
+			top: 200;
+			bottom: 0;
+			render
 !
 
 counter
@@ -158,7 +158,7 @@ initialize
 	self new build
 ! !
 
-MKModel subclass: #MKCounterModel
+MKObservable subclass: #MKCounterModel
 	instanceVariableNames: 'count text checked options selectedOption'
 	package: 'Moka-Examples'!
 

+ 199 - 70
st/Moka-Views.st

@@ -153,7 +153,7 @@ defaultLayout
 		yourself
 ! !
 
-MKView subclass: #MKContainerView
+MKLayoutView subclass: #MKContainerView
 	instanceVariableNames: 'childView'
 	package: 'Moka-Views'!
 !MKContainerView commentStamp!
@@ -170,6 +170,10 @@ childView
 childView: aView
 	childView := aView.
 	self update
+!
+
+children
+	^ { self childView }
 ! !
 
 !MKContainerView methodsFor: 'rendering'!
@@ -254,9 +258,15 @@ tag
 	^ 'h', self level asString
 ! !
 
-MKView subclass: #MKOverlayView
+MKLayoutView subclass: #MKOverlayView
 	instanceVariableNames: 'childView'
 	package: 'Moka-Views'!
+!MKOverlayView commentStamp!
+I display an transparent overlay, typically over other views, except my `childView`.
+
+## API
+
+Create instances using the class-side `childView:` method.!
 
 !MKOverlayView methodsFor: 'accessing'!
 
@@ -268,6 +278,10 @@ childView: aView
 	childView := aView
 !
 
+children
+	^ { self childView }
+!
+
 cssClass
 	^ super cssClass, ' mk_overlay'
 ! !
@@ -298,7 +312,7 @@ childView: aView
 		yourself
 ! !
 
-MKView subclass: #MKPaneView
+MKLayoutView subclass: #MKPaneView
 	instanceVariableNames: 'views'
 	package: 'Moka-Views'!
 !MKPaneView commentStamp!
@@ -310,6 +324,10 @@ Use `#addView:` to add a view to the pane.!
 
 !MKPaneView methodsFor: 'accessing'!
 
+children
+	^ self views
+!
+
 cssClass
 	^ super cssClass, ' mk_pane'
 !
@@ -360,64 +378,6 @@ renderContentOn: html
 		html with: each ]
 ! !
 
-MKPaneView subclass: #MKModalPaneView
-	instanceVariableNames: 'overlay closeOnEnter closeOnClick'
-	package: 'Moka-Views'!
-
-!MKModalPaneView methodsFor: 'accessing'!
-
-closeOnClick
-	^ closeOnClick ifNil: [ false ]
-!
-
-closeOnClick: aBoolean
-	closeOnClick := aBoolean
-!
-
-closeOnEnter
-	^ closeOnEnter ifNil: [ false ]
-!
-
-closeOnEnter: aBoolean
-	closeOnEnter := aBoolean
-!
-
-cssClass
-	^ super cssClass, ' mk_modal'
-!
-
-overlay
-	^ overlay ifNil: [ overlay := MKOverlayView childView: self ]
-!
-
-zindex
-	^ 1001
-! !
-
-!MKModalPaneView methodsFor: 'defaults'!
-
-defaultControllerClass
-	^ MKModalPaneController
-!
-
-defaultLayout
-	^ super defaultLayout
-		centerY: 0;
-		centerX: 0;
-		width: 300;
-		height: 200;
-		yourself
-! !
-
-!MKModalPaneView methodsFor: 'rendering'!
-
-renderOn: html
-	super renderOn: html.
-	root at: 'tabindex' put: '0'.
-	root asJQuery focus.
-	html with: self overlay
-! !
-
 MKPaneView subclass: #MKPanelView
 	instanceVariableNames: ''
 	package: 'Moka-Views'!
@@ -514,7 +474,10 @@ update: anAnnouncement
 
 popupList
 	"Show a new list view inside a modal pane"
-	self modalPaneView render.
+	self modalPaneView 
+		left: self domPosition x;
+		top: self domPosition y;
+		render.
 	self listView focus
 ! !
 
@@ -553,16 +516,16 @@ listView
 
 modalPaneView
 	^ modalPaneView ifNil: [
-		modalPaneView := MKModalPaneView new
+		modalPaneView := (MKModalDecorator decorate: self listView)
 			extraCssClass: 'mk_dropdown_pane';
 			closeOnEnter: true;
 			closeOnClick: true;
-			addView: self listView;
-			left: self domPosition x;
-			top: self domPosition y;
-			"Max height of the list"
-			height: 400;
-			yourself ]
+			yourself.
+		modalPaneView 
+			on: MKViewRemoved
+			send: #focus
+			to: self.
+		modalPaneView ]
 ! !
 
 MKSelectionView subclass: #MKListView
@@ -645,7 +608,9 @@ ensureVisible: aListItem
     aListItem position top < 0 ifTrue: [
 		(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem position top - 10) ].
     aListItem position top + aListItem height > parent height ifTrue: [ 
-		(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ]
+		(parent get: 0) scrollTop: ((parent get: 0) scrollTop + aListItem height - (parent height - aListItem position top)) +10 ].
+	
+	self announce: (MKViewScroll view: self)
 !
 
 positionOf: aListItem
@@ -725,6 +690,170 @@ cssClass
 	^ super cssClass, ' mk_sourcelist'
 ! !
 
+MKLayoutView subclass: #MKSplitView
+	instanceVariableNames: 'firstView secondView splitter'
+	package: 'Moka-Views'!
+!MKSplitView commentStamp!
+I am the superclass of all split views. I arrange two child view with a splitter between them.
+
+## API
+
+Create instances using the class-side method `firstView:secondView:`.!
+
+!MKSplitView methodsFor: 'accessing'!
+
+children
+	^ { self firstView. self secondView }
+!
+
+cssClass
+	^ super cssClass, ' mk_split_view'
+!
+
+firstView
+	^ firstView
+!
+
+firstView: aView
+	firstView := aView
+!
+
+secondView
+	^ secondView
+!
+
+secondView: aView
+	secondView := aView
+!
+
+splitterCssClass
+	^ 'mk_splitter'
+! !
+
+!MKSplitView methodsFor: 'defaults'!
+
+defaultThickness
+	^ 300
+! !
+
+!MKSplitView methodsFor: 'private'!
+
+setupSplitter
+	self subclassResponsibility
+! !
+
+!MKSplitView methodsFor: 'rendering'!
+
+renderContentOn: html
+	html with: self firstView.
+	splitter := html div class: self splitterCssClass.
+	html with: self secondView.
+        
+	self setupSplitter
+! !
+
+!MKSplitView class methodsFor: 'instance creation'!
+
+firstView: aView secondView: anotherView
+	^ self new
+		firstView: aView;
+		secondView: anotherView;
+		yourself
+! !
+
+MKSplitView subclass: #MKHorizontalSplitView
+	instanceVariableNames: 'leftThickness rightThickness'
+	package: 'Moka-Views'!
+!MKHorizontalSplitView commentStamp!
+I split my child views vertically.!
+
+!MKHorizontalSplitView methodsFor: 'accessing'!
+
+cssClass
+	^ super cssClass, ' horizontal'
+!
+
+leftThickness
+	^ leftThickness ifNil: [ self defaultThickness ]
+!
+
+leftThickness: aNumber
+	rightThickness := nil.
+	leftThickness := aNumber
+!
+
+rightThickness
+	^ rightThickness
+!
+
+rightThickness: aNumber
+	leftThickness := nil.
+	rightThickness := aNumber
+! !
+
+!MKHorizontalSplitView methodsFor: 'actions'!
+
+resize: aNumber
+    self firstView asJQuery css: 'width' put: aNumber asMokaCssString.
+	splitter asJQuery css: 'left' put: aNumber asMokaCssString.
+	self secondView asJQuery css: 'left' put: aNumber asMokaCssString
+! !
+
+!MKHorizontalSplitView methodsFor: 'private'!
+
+setupSplitter
+	splitter asJQuery draggable: #{ 
+    	'axis' -> 'x'. 
+        'containment' -> splitter asJQuery parent.
+        'helper' -> 'clone'.
+		'cursor' -> 'ew-resize'.
+		'stop' -> [ self resized ].
+        'drag' -> [ :e :ui | self resize: ui offset left ] }
+! !
+
+!MKHorizontalSplitView methodsFor: 'rendering'!
+
+renderContentOn: html
+	self leftThickness ifNotNil: [ :thickness |
+		self firstView width: thickness.
+		self secondView left: thickness ].
+		
+	super renderContentOn: html.
+	splitter asJQuery css: 'left' put: self leftThickness
+! !
+
+MKSplitView subclass: #MKVerticalSplitView
+	instanceVariableNames: ''
+	package: 'Moka-Views'!
+!MKVerticalSplitView commentStamp!
+I split my child views horizontally.!
+
+!MKVerticalSplitView methodsFor: 'accessing'!
+
+cssClass
+	^ super cssClass, ' vertical'
+! !
+
+!MKVerticalSplitView methodsFor: 'actions'!
+
+resize: aNumber
+    self firstView asJQuery css: 'right' put: aNumber asMokaCssString.
+	splitter asJQuery css: 'left' put: aNumber asMokaCssString.
+	self secondView asJQuery css: 'left' put: aNumber asMokaCssString
+! !
+
+!MKVerticalSplitView methodsFor: 'private'!
+
+setupSplitter
+	splitter asJQuery draggable: #{ 
+    	'axis' -> 'x'. 
+        'containment' -> splitter asJQuery parent.
+        'helper' -> 'clone'.
+		'cursor' -> 'ns-resize'.
+		'stop' -> [ self resized ].
+        'drag' -> [ :e :ui | self resize: (ui offset left) ] }
+! !
+
 MKSingleAspectView subclass: #MKTextAreaView
 	instanceVariableNames: ''
 	package: 'Moka-Views'!

+ 1 - 1
support/amber.js

@@ -56,7 +56,7 @@ require = function (require) {
             'amber_html': amber_home,
             'jquery': library_home + '/jquery/jquery.min',
             'jquery-ui': amber_home + '/support/jQuery/jquery-ui-1.8.24.custom.min',
-			'mousewheel': library_home + '/jscrollpane/script/jquery.mousewheel'
+			'mousewheel': library_home + '/jquery-mousewheel/jquery.mousewheel'
         },
         map: {
             '*': {

Some files were not shown because too many files changed in this diff