Browse Source

- Moka refactorings
- First attempt at decorators and scrolling

Nicolas Petton 10 years ago
parent
commit
501cd1afcf
16 changed files with 1271 additions and 238 deletions
  1. 130 50
      css/moka.css
  2. 61 43
      css/moka.less
  3. BIN
      images/moka/switch.png
  4. 1 1
      index.html
  5. 16 0
      js/Kernel-Objects.js
  6. 87 3
      js/Moka-Controllers.js
  7. 282 33
      js/Moka-Core.js
  8. 340 0
      js/Moka-Decorators.js
  9. 96 76
      js/Moka-Examples.js
  10. 8 8
      js/Moka-Views.js
  11. 4 0
      st/Kernel-Objects.st
  12. 26 1
      st/Moka-Controllers.st
  13. 88 9
      st/Moka-Core.st
  14. 113 0
      st/Moka-Decorators.st
  15. 16 11
      st/Moka-Examples.st
  16. 3 3
      st/Moka-Views.st

+ 130 - 50
css/moka.css

@@ -8,43 +8,6 @@
   -ms-user-select: none;
   user-select: none;
 }
-::-webkit-scrollbar {
-  width: 8px;
-  height: 8px;
-}
-::-webkit-scrollbar-button {
-  height: 0;
-  width: 0;
-}
-::-webkit-resizer {
-  background-color: #eeeeee;
-}
-::-webkit-scrollbar-track {
-  background-color: #eeeeee;
-}
-::-webkit-scrollbar-track-piece {
-  background-color: #eeeeee;
-}
-::-webkit-scrollbar-corner {
-  background-color: #eeeeee;
-}
-::-webkit-scrollbar-thumb {
-  background: #e1e1e1;
-  background: -webkit-gradient(linear, left top, right top, color-stop(0, #e1e1e1), color-stop(1, #cfcfcf));
-  background: -ms-linear-gradient(left, #e1e1e1, #cfcfcf);
-  background: -moz-linear-gradient(right, #e1e1e1 0%, #cfcfcf 100%);
-  border: 1px solid #959595;
-  -webkit-border-radius: 8px;
-  -moz-border-radius: 8px;
-  border-radius: 8px;
-}
-::-webkit-scrollbar-thumb:hover {
-  background: #c3c3c3;
-  background: -webkit-gradient(linear, left top, right top, color-stop(0, #c3c3c3), color-stop(1, #aaaaaa));
-  background: -ms-linear-gradient(left, #c3c3c3, #aaaaaa);
-  background: -moz-linear-gradient(right, #c3c3c3 0%, #aaaaaa 100%);
-  border: 1px solid #919191;
-}
 .mk_default {
   font-size: 12px;
   font-family: "Open Sans";
@@ -106,7 +69,6 @@
   background: #eeeeee;
   border-color: #888888;
   border-style: solid;
-  overflow: auto;
 }
 .moka_view.mk_pane.mk_modal {
   z-index: 1001;
@@ -116,6 +78,46 @@
 .moka_view.mk_pane.mk_modal:focus {
   outline: 0 none;
 }
+.moka_view.mk_scroll .mk_scroll_rail {
+  position: absolute;
+  z-index: 1;
+}
+.moka_view.mk_scroll .mk_scroll_rail .mk_scrollbar {
+  position: absolute;
+  -webkit-border-radius: 8px;
+  -moz-border-radius: 8px;
+  border-radius: 8px;
+  -webkit-transition: background 0.2s ease-out;
+  -moz-transition: background 0.2s ease-out;
+  -o-transition: background 0.2s ease-out;
+  transition: background 0.2s ease-out;
+  opacity: 0.6;
+}
+.moka_view.mk_scroll .mk_scroll_rail .mk_scrollbar:hover,
+.moka_view.mk_scroll .mk_scroll_rail .mk_scrollbar.ui-draggable-dragging {
+  background: #333333;
+}
+.moka_view.mk_scroll .mk_scroll_rail.vertical {
+  top: 0;
+  right: 0;
+  bottom: 0;
+  width: 10px;
+}
+.moka_view.mk_scroll .mk_scroll_rail.vertical .mk_scrollbar {
+  width: 8px;
+}
+.moka_view.mk_scroll .mk_scroll_rail.horizontal {
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 10px;
+}
+.moka_view.mk_scroll .mk_scroll_rail.horizontal .mk_scrollbar {
+  height: 8px;
+}
+.moka_view.mk_scroll:hover .mk_scrollbar {
+  background: rgba(0, 0, 0, 0.4);
+}
 .moka_view .mk_control,
 .moka_view .mk_control:active,
 .moka_view .mk_control:focus {
@@ -132,7 +134,7 @@
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;
   border-radius: 3px;
-  border: 1px solid #aaaaaa;
+  border: 1px solid #919191;
   -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
   -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
   box-shadow: inset 0 0 3px 0 #c3c3c3;
@@ -148,6 +150,11 @@
 .moka_view .mk_control:focus:focus {
   outline: 0;
 }
+.moka_view .mk_control::-moz-focus-inner,
+.moka_view .mk_control:active::-moz-focus-inner,
+.moka_view .mk_control:focus::-moz-focus-inner {
+  border: 0;
+}
 .moka_view.mk_button {
   font-size: 12px;
   font-family: "Open Sans";
@@ -162,7 +169,7 @@
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;
   border-radius: 3px;
-  border: 1px solid #aaaaaa;
+  border: 1px solid #919191;
   -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
   -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
   box-shadow: inset 0 0 3px 0 #c3c3c3;
@@ -190,13 +197,16 @@
 .moka_view.mk_button:focus {
   outline: 0;
 }
+.moka_view.mk_button::-moz-focus-inner {
+  border: 0;
+}
 .moka_view.mk_button.default {
   background: #9ec4eb;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #9ec4eb), color-stop(1, #deeaf8));
   background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
   background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
   background: -o-linear-gradient(#deeaf8, #9ec4eb);
-  border-color: #9d9d9d;
+  text-shadow: 0 1px 0 #eeeeee;
 }
 .moka_view.mk_button.default:active {
   background: #9ec4eb;
@@ -204,6 +214,7 @@
 }
 .moka_view.mk_button:focus {
   border-color: #4a90d9;
+  box-shadow: 0 0 5px #4a90d9;
 }
 .moka_view.mk_button:active {
   background: #dddddd;
@@ -227,7 +238,7 @@
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;
   border-radius: 3px;
-  border: 1px solid #aaaaaa;
+  border: 1px solid #919191;
   -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
   -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
   box-shadow: inset 0 0 3px 0 #c3c3c3;
@@ -245,6 +256,10 @@
 .moka_view.mk_textarea:focus {
   outline: 0;
 }
+.moka_view.mk_input::-moz-focus-inner,
+.moka_view.mk_textarea::-moz-focus-inner {
+  border: 0;
+}
 .moka_view.mk_input:focus,
 .moka_view.mk_textarea:focus {
   -webkit-box-shadow: inset 0 0 3px 0 #888888;
@@ -270,12 +285,43 @@
   background-image: url('../images/moka/check-active.png');
 }
 .moka_view.mk_switch {
+  font-size: 12px;
+  font-family: "Open Sans";
+  line-height: 1.5em;
+  color: #444444;
+  text-shadow: 0 1px 0 white;
+  padding: 0;
+  margin: 0;
+  position: absolute;
+  display: block;
+  overflow: hidden;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  border: 1px solid #919191;
+  -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
+  -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
+  box-shadow: inset 0 0 3px 0 #c3c3c3;
+  background-color: white;
+  background: -webkit-linear-gradient(top, #eee 0%, white 6px);
+  background: -moz-linear-gradient(top, #eee 0%, white 6px);
+  background: -ms-linear-gradient(top, #eee 0%, white 6px);
+  background: -o-linear-gradient(top, #eee 0%, white 6px);
+  background: linear-gradient(top, #eee 0%, white 6px);
   -webkit-touch-callout: none;
   -webkit-user-select: none;
   -khtml-user-select: none;
   -moz-user-select: none;
   -ms-user-select: none;
   user-select: none;
+  -webkit-box-shadow: 0 1px 1px 0 #d8d8d8;
+  -moz-box-shadow: 0 1px 1px 0 #d8d8d8;
+  box-shadow: 0 1px 1px 0 #d8d8d8;
+  background: #fafafa;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #dedede), color-stop(1, #ffffff));
+  background: -ms-linear-gradient(bottom, #dedede, #ffffff);
+  background: -moz-linear-gradient(center bottom, #dedede 0%, #ffffff 100%);
+  background: -o-linear-gradient(#ffffff, #dedede);
   -webkit-border-radius: 20px;
   -moz-border-radius: 20px;
   border-radius: 20px;
@@ -283,19 +329,47 @@
   display: inline-block;
   height: 20px;
   width: 39px;
-  border: 1px solid #888888;
   background: url('../images/moka/switch.png') 100% 50% no-repeat;
   -webkit-transition: background 0.2s ease-out;
   -moz-transition: background 0.2s ease-out;
   -o-transition: background 0.2s ease-out;
   transition: background 0.2s ease-out;
+  border-color: #6f6f6f;
+}
+.moka_view.mk_switch:focus {
+  outline: 0;
+}
+.moka_view.mk_switch::-moz-focus-inner {
+  border: 0;
+}
+.moka_view.mk_switch.default {
+  background: #9ec4eb;
+  background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #9ec4eb), color-stop(1, #deeaf8));
+  background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
+  background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
+  background: -o-linear-gradient(#deeaf8, #9ec4eb);
+  text-shadow: 0 1px 0 #eeeeee;
+}
+.moka_view.mk_switch.default:active {
+  background: #9ec4eb;
+  border-color: #4a90d9;
 }
 .moka_view.mk_switch:focus {
-  border-color: #256ab1;
+  border-color: #4a90d9;
+  box-shadow: 0 0 5px #4a90d9;
+}
+.moka_view.mk_switch:active {
+  background: #dddddd;
+  -webkit-box-shadow: inset 0 0 3px 0 #888888;
+  -moz-box-shadow: inset 0 0 3px 0 #888888;
+  box-shadow: inset 0 0 3px 0 #888888;
+  border-color: #947c7c;
+}
+.moka_view.mk_switch:active {
+  background: url('../images/moka/switch.png') 100% 50% no-repeat;
 }
 .moka_view.mk_switch.checked {
   background: url('../images/moka/switch.png') 0% 50% no-repeat;
-  border-color: #3583d5;
 }
 .moka_view.mk_list {
   cursor: default;
@@ -317,7 +391,7 @@
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;
   border-radius: 3px;
-  border: 1px solid #aaaaaa;
+  border: 1px solid #919191;
   -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
   -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
   box-shadow: inset 0 0 3px 0 #c3c3c3;
@@ -328,12 +402,14 @@
   background: -o-linear-gradient(top, #eee 0%, white 6px);
   background: linear-gradient(top, #eee 0%, white 6px);
   padding: 0;
-  overflow: auto;
   background: white;
 }
 .moka_view.mk_list:focus {
   outline: 0;
 }
+.moka_view.mk_list::-moz-focus-inner {
+  border: 0;
+}
 .moka_view.mk_list:focus {
   outline: 0;
   border-color: #4a90d9;
@@ -392,7 +468,7 @@
   -webkit-border-radius: 3px;
   -moz-border-radius: 3px;
   border-radius: 3px;
-  border: 1px solid #aaaaaa;
+  border: 1px solid #919191;
   -webkit-box-shadow: inset 0 0 3px 0 #c3c3c3;
   -moz-box-shadow: inset 0 0 3px 0 #c3c3c3;
   box-shadow: inset 0 0 3px 0 #c3c3c3;
@@ -422,13 +498,16 @@
 .moka_view.mk_dropdown:focus {
   outline: 0;
 }
+.moka_view.mk_dropdown::-moz-focus-inner {
+  border: 0;
+}
 .moka_view.mk_dropdown.default {
   background: #9ec4eb;
   background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #9ec4eb), color-stop(1, #deeaf8));
   background: -ms-linear-gradient(bottom, #9ec4eb, #deeaf8);
   background: -moz-linear-gradient(center bottom, #9ec4eb 0%, #deeaf8 100%);
   background: -o-linear-gradient(#deeaf8, #9ec4eb);
-  border-color: #9d9d9d;
+  text-shadow: 0 1px 0 #eeeeee;
 }
 .moka_view.mk_dropdown.default:active {
   background: #9ec4eb;
@@ -436,6 +515,7 @@
 }
 .moka_view.mk_dropdown:focus {
   border-color: #4a90d9;
+  box-shadow: 0 0 5px #4a90d9;
 }
 .moka_view.mk_dropdown:active {
   background: #dddddd;
@@ -453,7 +533,7 @@
   background-image: url(/images/moka/dropdown_arrows.png);
   background-position: center;
   background-repeat: no-repeat;
-  border-left: 1px solid #aaaaaa;
+  border-left: 1px solid #919191;
 }
 .moka_view.mk_dropdown_pane {
   padding: 6px;

+ 61 - 43
css/moka.less

@@ -94,40 +94,6 @@
 @verydark: #333;
 
 
-// scrollbars
-
-::-webkit-scrollbar {
-    width: 8px; 
-    height: 8px;
-}
-::-webkit-scrollbar-button {
-    height: 0; 
-    width: 0;
-}
-::-webkit-resizer {
-    background-color: @light;
-}
-::-webkit-scrollbar-track {
-    background-color: @light;
-}
-::-webkit-scrollbar-track-piece {
-    background-color: @light;
-}
-::-webkit-scrollbar-corner {
-    background-color: @light
-}
-::-webkit-scrollbar-thumb {
-    .vertical-gradient(darken(@light, 5%), darken(@light, 5%), darken(@light, 12%));
-    border: 1px solid darken(@light, 35%);
-    .rounded(8px);
-    
-    &:hover {
-	.vertical-gradient(lighten(@grey, 10%), lighten(@grey, 10%), @grey);
-	border: 1px solid darken(@grey, 10%);
-    }
-}
-
-
 // mixins
 
 .mk_default {
@@ -198,7 +164,6 @@
 	    background: @light;
 	    border-color: @dark;
 	    border-style: solid;
-	    overflow: auto;
 	}
 
 	&.mk_modal {
@@ -212,13 +177,62 @@
 	}
     }
 
+    // Scrollbars
+
+    &.mk_scroll {
+
+	.mk_scroll_rail {
+	    position: absolute;
+	    z-index: 1;
+
+	    .mk_scrollbar {
+		position: absolute;
+		.rounded(8px);
+		.transition(background, 0.2s);
+		opacity: 0.6;
+
+		&:hover, &.ui-draggable-dragging {
+		    background: @verydark;
+		}
+	    }
+
+	    &.vertical {
+		top: 0;
+		right: 0;
+		bottom: 0;
+		width: 10px;
+
+		.mk_scrollbar {
+		    width: 8px;
+		}
+	    }
+
+	    &.horizontal {
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 10px;
+
+		.mk_scrollbar {
+		    height: 8px;
+		}
+	    }
+	}
+
+	&:hover {
+	    .mk_scrollbar {
+		background: rgba(0,0,0,0.4);
+	    }
+	}
+    }
+
     // Controls
     
     .mk_control, .mk_control:active, .mk_control:focus {
 	.mk_default;
 	.mk_absolute;
 	.rounded();
-	border: 1px solid @grey;
+	border: 1px solid darken(@grey, 10%);
 	.box-shadow(inset 0 0 3px 0 lighten(@grey, 10%));
 	background-color: white;
 	// see http://stackoverflow.com/questions/14768780/how-can-i-pass-mixin-arguments-along-literally-in-less-css
@@ -227,6 +241,10 @@
 	&:focus {
 	    outline: 0;
 	}
+
+	&::-moz-focus-inner {
+	    border: 0;
+	}
     }
 
     &.mk_button {
@@ -237,7 +255,7 @@
 
 	&.default {
 	    .gradient(lighten(@blue, 20%), lighten(@blue, 20%), lighten(@blue, 35%));
-	    border-color: darken(@grey, 5%);
+	    text-shadow: 0 1px 0 @light;
 
 	    &:active {
 	    	background: lighten(@blue, 20%);
@@ -247,6 +265,7 @@
 
 	&:focus {
 	    border-color: @blue;
+	    box-shadow: 0 0 5px @blue;
 	}
 
 	&:active {
@@ -285,22 +304,22 @@
 
     &.mk_switch {
 	.no-select;
+	.mk_button;
 	.rounded(20px);
 	vertical-align: middle;
 	display: inline-block;
 	height: 20px;
 	width: 39px;
-	border: 1px solid @dark;
 	background: url('../images/moka/switch.png') 100% 50% no-repeat;
 	.transition(background, .2s);
+	border-color: darken(@dark, 10%);
 
-	&:focus {
-	    border-color: darken(@blue, 15%);
+	&:active {
+	    background: url('../images/moka/switch.png') 100% 50% no-repeat;
 	}
 
 	&.checked {
 	    background: url('../images/moka/switch.png') 0% 50% no-repeat;
-	    border-color: darken(@blue, 5%);
 	}
     }
 
@@ -313,7 +332,6 @@
 	.no-select;
 	.mk_control;
 	padding: 0;
-	overflow: auto;
 	background: white;
 
 	&:focus {
@@ -375,7 +393,7 @@
 	    background-image: url(/images/moka/dropdown_arrows.png);
 	    background-position: center;
 	    background-repeat: no-repeat;
-	    border-left: 1px solid @grey;
+	    border-left: 1px solid darken(@grey, 10%);
 	}
     }
 

BIN
images/moka/switch.png


+ 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-Layouts", "amber_core/Moka-Examples"],
+        ["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"],
         function (smalltalk) {
             smalltalk.defaultAmdNamespace = "amber_core";
             smalltalk.initialize();

+ 16 - 0
js/Kernel-Objects.js

@@ -3334,6 +3334,22 @@ referencedClasses: []
 }),
 smalltalk.Number.klass);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "fromNumber:",
+category: 'instance creation',
+fn: function (aNumber){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return Number(aNumber);
+return self}, function($ctx1) {$ctx1.fill(self,"fromNumber:",{aNumber:aNumber},smalltalk.Number.klass)})},
+args: ["aNumber"],
+source: "fromNumber: aNumber\x0a\x09<return Number(aNumber)>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.Number.klass);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "heliosClass",

+ 87 - 3
js/Moka-Controllers.js

@@ -549,12 +549,12 @@ var $1;
 smalltalk.MKDropdownListController.superclass.fn.prototype._onKeyDown_.apply(_st(self), [anEvent]);
 $1=_st(_st(anEvent)._keyCode()).__eq(_st(_st($String())._cr())._asciiValue());
 if(smalltalk.assert($1)){
-self._selectItem_(self._itemForTarget_(_st(anEvent)._target()));
+self._selectItem_(_st(self._view())._activeItem());
 };
 return self}, function($ctx1) {$ctx1.fill(self,"onKeyDown:",{anEvent:anEvent},smalltalk.MKDropdownListController)})},
 args: ["anEvent"],
-source: "onKeyDown: anEvent\x0a\x09super onKeyDown: anEvent.\x0a\x09\x0a\x09anEvent keyCode = String cr asciiValue ifTrue: [\x0a\x09\x09self selectItem: (self itemForTarget: anEvent target) ]",
-messageSends: ["onKeyDown:", "ifTrue:", "=", "keyCode", "asciiValue", "cr", "selectItem:", "itemForTarget:", "target"],
+source: "onKeyDown: anEvent\x0a\x09super onKeyDown: anEvent.\x0a\x09\x0a\x09anEvent keyCode = String cr asciiValue ifTrue: [\x0a\x09\x09self selectItem: self view activeItem ]",
+messageSends: ["onKeyDown:", "ifTrue:", "=", "keyCode", "asciiValue", "cr", "selectItem:", "activeItem", "view"],
 referencedClasses: ["String"]
 }),
 smalltalk.MKDropdownListController);
@@ -810,4 +810,88 @@ referencedClasses: []
 smalltalk.MKRepeater);
 
 
+
+smalltalk.addClass('MKScrollController', smalltalk.MKController, [], 'Moka-Controllers');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onHorizontalDrag:",
+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());
+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"],
+referencedClasses: []
+}),
+smalltalk.MKScrollController);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "onVerticalDrag:",
+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)})},
+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"],
+referencedClasses: []
+}),
+smalltalk.MKScrollController);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "position",
+category: 'accessing',
+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)})},
+args: [],
+source: "position\x0a\x09^ self view domDecoratedSize * self scrollPercent",
+messageSends: ["*", "domDecoratedSize", "view", "scrollPercent"],
+referencedClasses: []
+}),
+smalltalk.MKScrollController);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollPercent",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $3,$2,$6,$5,$4,$1;
+$3=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"],
+referencedClasses: []
+}),
+smalltalk.MKScrollController);
+
+
 });

+ 282 - 33
js/Moka-Core.js

@@ -463,6 +463,24 @@ smalltalk.MKModelChanged.klass);
 
 smalltalk.addClass('MKView', smalltalk.Widget, ['controller', 'model', 'root', 'layout', 'extraCssClass'], 'Moka-Core');
 smalltalk.MKView.comment="I implement the View part of the MVC pattern in Moka.\x0a\x0a## API\x0a- Instance can be created with the `MKView class >> model:*` convenience methods\x0a- rendering is done through `#renderContentOn:`, to be overridden in concrete view classes\x0a- `#update` provide updating facility, refreshing the entire view\x0a- subclasses can override `#defaultControllerClass` to provide a default controller specific to a view\x0a- subclasses can override `#observeModel`\x0a- Extra css classes can be added with `#extraCssClass:`.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "asJQuery",
+category: 'converting',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self["@root"])._asJQuery();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"asJQuery",{},smalltalk.MKView)})},
+args: [],
+source: "asJQuery\x0a\x09^ root asJQuery",
+messageSends: ["asJQuery"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "blur",
@@ -485,6 +503,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "bottom",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._bottom();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"bottom",{},smalltalk.MKView)})},
+args: [],
+source: "bottom\x0a\x09^ self layout bottom",
+messageSends: ["bottom", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "bottom:",
@@ -501,6 +537,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "centerX",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._centerX();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"centerX",{},smalltalk.MKView)})},
+args: [],
+source: "centerX\x0a\x09^ self layout centerX",
+messageSends: ["centerX", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "centerX:",
@@ -517,6 +571,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "centerY",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._centerY();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"centerY",{},smalltalk.MKView)})},
+args: [],
+source: "centerY\x0a\x09^ self layout centerY",
+messageSends: ["centerY", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "centerY:",
@@ -564,11 +636,10 @@ category: 'accessing',
 fn: function (aController){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2;
+var $1;
 self["@controller"]=aController;
-$1=aController;
-_st($1)._view_(self);
-$2=_st($1)._model_(self._model());
+_st(aController)._view_(self);
+$1=_st(aController)._model_(self._model());
 return self}, function($ctx1) {$ctx1.fill(self,"controller:",{aController:aController},smalltalk.MKView)})},
 args: ["aController"],
 source: "controller: aController\x0a\x09\x22Install `aController` to be the receiver's controller\x22\x0a\x09\x0a\x09controller := aController.\x0a\x09aController \x0a\x09\x09view: self;\x0a\x09\x09model: self model",
@@ -633,9 +704,7 @@ fn: function (){
 var self=this;
 function $MKController(){return smalltalk.MKController||(typeof MKController=="undefined"?nil:MKController)}
 return smalltalk.withContext(function($ctx1) { 
-var $1;
-$1=$MKController();
-return $1;
+return $MKController();
 }, function($ctx1) {$ctx1.fill(self,"defaultControllerClass",{},smalltalk.MKView)})},
 args: [],
 source: "defaultControllerClass\x0a\x09^ MKController",
@@ -669,6 +738,47 @@ referencedClasses: ["MKLayout"]
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domPosition",
+category: 'dom',
+fn: function (){
+var self=this;
+var offset;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+offset=_st(self._asJQuery())._offset();
+$1=_st(_st(offset)._left()).__at(_st(offset)._top());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domPosition",{offset:offset},smalltalk.MKView)})},
+args: [],
+source: "domPosition\x0a\x09\x22Answer the position of the reciever in the page\x22\x0a\x09\x0a\x09| offset |\x0a\x09offset := self asJQuery offset.\x0a\x09^ offset left @ offset top",
+messageSends: ["offset", "asJQuery", "@", "left", "top"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domSize",
+category: 'dom',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $3,$2,$1;
+$3=self._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$2=_st($3)._width();
+$1=_st($2).__at(_st(self._asJQuery())._height());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domSize",{},smalltalk.MKView)})},
+args: [],
+source: "domSize\x0a\x09^ self asJQuery width @ self asJQuery height",
+messageSends: ["@", "width", "asJQuery", "height"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "extraCssClass",
@@ -730,6 +840,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "height",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._height();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"height",{},smalltalk.MKView)})},
+args: [],
+source: "height\x0a\x09^ self layout height",
+messageSends: ["height", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "height:",
@@ -770,6 +898,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "left",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._left();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"left",{},smalltalk.MKView)})},
+args: [],
+source: "left\x0a\x09^ self layout left",
+messageSends: ["left", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "left:",
@@ -836,32 +982,6 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
-smalltalk.addMethod(
-smalltalk.method({
-selector: "position",
-category: 'accessing',
-fn: function (){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-var $2,$1;
-$2=self["@root"];
-if(($receiver = $2) == nil || $receiver == null){
-$1=$2;
-} else {
-var offset;
-offset=_st(_st(self["@root"])._asJQuery())._offset();
-offset;
-$1=_st(_st(offset)._left()).__at(_st(offset)._top());
-};
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"position",{},smalltalk.MKView)})},
-args: [],
-source: "position\x0a\x09\x22Answer the position of the reciever in the page\x22\x0a\x09\x0a\x09^ root ifNotNil: [ \x0a\x09\x09|  offset |\x0a\x09\x09offset := root asJQuery offset.\x0a\x09\x09offset left @ offset top ]",
-messageSends: ["ifNotNil:", "offset", "asJQuery", "@", "left", "top"],
-referencedClasses: []
-}),
-smalltalk.MKView);
-
 smalltalk.addMethod(
 smalltalk.method({
 selector: "remove",
@@ -941,6 +1061,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "right",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._right();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"right",{},smalltalk.MKView)})},
+args: [],
+source: "right\x0a\x09^ self layout right",
+messageSends: ["right", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "right:",
@@ -1065,6 +1203,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "top",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._top();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"top",{},smalltalk.MKView)})},
+args: [],
+source: "top\x0a\x09^ self layout top",
+messageSends: ["top", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "top:",
@@ -1110,6 +1266,24 @@ referencedClasses: []
 }),
 smalltalk.MKView);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "width",
+category: 'layout',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(self._layout())._width();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"width",{},smalltalk.MKView)})},
+args: [],
+source: "width\x0a\x09^ self layout width",
+messageSends: ["width", "layout"],
+referencedClasses: []
+}),
+smalltalk.MKView);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "width:",
@@ -1357,4 +1531,79 @@ referencedClasses: []
 }),
 smalltalk.MKSingleAspectView.klass);
 
+
+smalltalk.addClass('MKDecorator', smalltalk.MKView, ['decorated'], 'Moka-Core');
+smalltalk.MKDecorator.comment="I am root class of the decorator pattern in Moka. \x0a\x0aI am used to add rendering and/or behavior to other views.\x0a\x0a## API\x0a\x0aTo decorate a view, use the class-side `#decorate:` method.";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "decorated",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=self["@decorated"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"decorated",{},smalltalk.MKDecorator)})},
+args: [],
+source: "decorated\x0a\x09^ decorated",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "decorated:",
+category: 'accessing',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+self["@decorated"]=aView;
+return self}, function($ctx1) {$ctx1.fill(self,"decorated:",{aView:aView},smalltalk.MKDecorator)})},
+args: ["aView"],
+source: "decorated: aView\x0a\x09decorated := aView",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.MKDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(html)._with_(self._decorated());
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.MKDecorator)})},
+args: ["html"],
+source: "renderContentOn: html\x0a\x09html with: self decorated",
+messageSends: ["with:", "decorated"],
+referencedClasses: []
+}),
+smalltalk.MKDecorator);
+
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "decorate:",
+category: 'instance creation',
+fn: function (aView){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $2,$3,$1;
+$2=self._new();
+_st($2)._decorated_(aView);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"decorate:",{aView:aView},smalltalk.MKDecorator.klass)})},
+args: ["aView"],
+source: "decorate: aView\x0a\x09^ self new\x0a\x09\x09decorated: aView;\x0a\x09\x09yourself",
+messageSends: ["decorated:", "new", "yourself"],
+referencedClasses: []
+}),
+smalltalk.MKDecorator.klass);
+
 });

+ 340 - 0
js/Moka-Decorators.js

@@ -0,0 +1,340 @@
+define("amber_core/Moka-Decorators", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_core/Moka-Core"], function(smalltalk,nil,_st){
+smalltalk.addPackage('Moka-Decorators');
+smalltalk.packages["Moka-Decorators"].transport = {"type":"amd","amdNamespace":"amber_core"};
+
+smalltalk.addClass('MKScrollDecorator', smalltalk.MKDecorator, ['verticalScrollbar', 'horizontalScrollbar'], 'Moka-Decorators');
+smalltalk.addMethod(
+smalltalk.method({
+selector: "cssClass",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(smalltalk.MKScrollDecorator.superclass.fn.prototype._cssClass.apply(_st(self), [])).__comma(" mk_scroll");
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"cssClass",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "cssClass\x0a\x09^ super cssClass, ' mk_scroll'",
+messageSends: [",", "cssClass"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "defaultControllerClass",
+category: 'defaults',
+fn: function (){
+var self=this;
+function $MKScrollController(){return smalltalk.MKScrollController||(typeof MKScrollController=="undefined"?nil:MKScrollController)}
+return smalltalk.withContext(function($ctx1) { 
+return $MKScrollController();
+}, function($ctx1) {$ctx1.fill(self,"defaultControllerClass",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "defaultControllerClass\x0a\x09^ MKScrollController",
+messageSends: [],
+referencedClasses: ["MKScrollController"]
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domDecoratedSize",
+category: 'dom',
+fn: function (){
+var self=this;
+var element;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+element=_st(_st(self._decorated())._asJQuery())._get_((0));
+$1=_st(_st(element)._scrollWidth()).__at(_st(element)._scrollHeight());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domDecoratedSize",{element:element},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "domDecoratedSize\x0a\x09| element |\x0a\x09element := self decorated asJQuery get: 0.\x0a\x09^ element scrollWidth @ element scrollHeight",
+messageSends: ["get:", "asJQuery", "decorated", "@", "scrollWidth", "scrollHeight"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domOverflow",
+category: 'dom',
+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)._scrollWidth()).__minus(_st(element)._clientWidth());
+$ctx1.sendIdx["-"]=1;
+$1=_st($2).__at(_st(_st(element)._scrollHeight()).__minus(_st(element)._clientHeight()));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domOverflow",{element:element},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "domOverflow\x0a\x09| element |\x0a\x09element := self decorated asJQuery get: 0.\x0a\x09^ (element scrollWidth - element clientWidth) @ (element scrollHeight - element clientHeight)",
+messageSends: ["get:", "asJQuery", "decorated", "@", "-", "scrollWidth", "clientWidth", "scrollHeight", "clientHeight"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domScrollPosition",
+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;
+$3=_st($4)._position();
+$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)})},
+args: [],
+source: "domScrollPosition\x0a\x09^ horizontalScrollbar asJQuery position left @ verticalScrollbar asJQuery position top",
+messageSends: ["@", "left", "position", "asJQuery", "top"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "domScrollbarSize",
+category: 'dom',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $3,$2,$1;
+$3=_st(self["@horizontalScrollbar"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$2=_st($3)._width();
+$1=_st($2).__at(_st(_st(self["@verticalScrollbar"])._asJQuery())._height());
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"domScrollbarSize",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "domScrollbarSize\x0a\x09^ horizontalScrollbar asJQuery width @ verticalScrollbar asJQuery height",
+messageSends: ["@", "width", "asJQuery", "height"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasHorizontalOverflow",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(self._domOverflow())._x()).__gt((0));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"hasHorizontalOverflow",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "hasHorizontalOverflow\x0a\x09^ self domOverflow x > 0",
+messageSends: [">", "x", "domOverflow"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "hasVerticalOverflow",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st(_st(self._domOverflow())._y()).__gt((0));
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"hasVerticalOverflow",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "hasVerticalOverflow\x0a\x09^ self domOverflow y > 0",
+messageSends: [">", "y", "domOverflow"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "renderContentOn:",
+category: 'rendering',
+fn: function (html){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3,$5,$6,$4,$7,$9,$10,$8;
+$1=_st(html)._div();
+$ctx1.sendIdx["div"]=1;
+_st($1)._class_("mk_scroll_container");
+$ctx1.sendIdx["class:"]=1;
+$2=_st($1)._with_((function(){
+return smalltalk.withContext(function($ctx2) {
+return smalltalk.MKScrollDecorator.superclass.fn.prototype._renderContentOn_.apply(_st(self), [html]);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
+$ctx1.sendIdx["with:"]=1;
+$3=_st(html)._div();
+$ctx1.sendIdx["div"]=2;
+_st($3)._class_("mk_scroll_rail vertical");
+$ctx1.sendIdx["class:"]=2;
+$4=_st($3)._with_((function(){
+return smalltalk.withContext(function($ctx2) {
+$5=_st(html)._div();
+$ctx2.sendIdx["div"]=3;
+_st($5)._class_("mk_scrollbar");
+$ctx2.sendIdx["class:"]=3;
+$6=_st($5)._yourself();
+$ctx2.sendIdx["yourself"]=1;
+self["@verticalScrollbar"]=$6;
+return self["@verticalScrollbar"];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)})}));
+$ctx1.sendIdx["with:"]=2;
+$7=_st(html)._div();
+$ctx1.sendIdx["div"]=4;
+_st($7)._class_("mk_scroll_rail horizontal");
+$ctx1.sendIdx["class:"]=4;
+$8=_st($7)._with_((function(){
+return smalltalk.withContext(function($ctx2) {
+$9=_st(html)._div();
+_st($9)._class_("mk_scrollbar");
+$10=_st($9)._yourself();
+self["@horizontalScrollbar"]=$10;
+return self["@horizontalScrollbar"];
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,3)})}));
+self._setupScrollbars();
+return self}, function($ctx1) {$ctx1.fill(self,"renderContentOn:",{html:html},smalltalk.MKScrollDecorator)})},
+args: ["html"],
+source: "renderContentOn: html\x0a\x09html div \x0a\x09\x09class: 'mk_scroll_container';\x0a\x09\x09with: [ super renderContentOn: html ].\x0a\x09\x0a\x09html div \x0a\x09\x09class: 'mk_scroll_rail vertical';\x0a\x09\x09with: [\x0a\x09\x09\x09verticalScrollbar := html div\x0a\x09\x09\x09\x09class: 'mk_scrollbar';\x0a\x09\x09\x09\x09yourself ].\x0a\x09html div \x0a\x09\x09class: 'mk_scroll_rail horizontal';\x0a\x09\x09with: [\x0a\x09\x09\x09horizontalScrollbar := html div\x0a\x09\x09\x09\x09class: 'mk_scrollbar';\x0a\x09\x09\x09\x09yourself ].\x0a\x09\x0a\x09self setupScrollbars",
+messageSends: ["class:", "div", "with:", "renderContentOn:", "yourself", "setupScrollbars"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "scrollbarSize",
+category: 'accessing',
+fn: function (){
+var self=this;
+var domSize,overflow;
+return smalltalk.withContext(function($ctx1) { 
+var $4,$6,$5,$3,$2,$9,$11,$10,$8,$7,$1;
+domSize=self._domSize();
+overflow=self._domOverflow();
+$4=_st(domSize)._x();
+$ctx1.sendIdx["x"]=1;
+$6=_st(overflow)._x();
+$ctx1.sendIdx["x"]=2;
+$5=_st($6).__plus(_st(domSize)._x());
+$ctx1.sendIdx["+"]=1;
+$3=_st($4).__slash($5);
+$ctx1.sendIdx["/"]=1;
+$2=_st($3).__star((100));
+$ctx1.sendIdx["*"]=1;
+$9=_st(domSize)._y();
+$ctx1.sendIdx["y"]=1;
+$11=_st(overflow)._y();
+$ctx1.sendIdx["y"]=2;
+$10=_st($11).__plus(_st(domSize)._y());
+$8=_st($9).__slash($10);
+$7=_st($8).__star((100));
+$1=_st($2).__at($7);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"scrollbarSize",{domSize:domSize,overflow:overflow},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "scrollbarSize\x0a\x09| domSize overflow |\x0a\x09\x0a\x09domSize := self domSize.\x0a\x09overflow := self domOverflow.\x0a\x09^ ((domSize x / (overflow x + domSize x)) * 100) @ ((domSize y / (overflow y + domSize y) * 100))",
+messageSends: ["domSize", "domOverflow", "@", "*", "/", "x", "+", "y"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "setupScrollbars",
+category: 'private',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$3,$4,$6,$5,$2,$7,$9,$10,$8;
+$1=_st(self["@verticalScrollbar"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$3="containment".__minus_gt("parent");
+$ctx1.sendIdx["->"]=1;
+$4="axis".__minus_gt("y");
+$ctx1.sendIdx["->"]=2;
+$5="drag".__minus_gt((function(event){
+return smalltalk.withContext(function($ctx2) {
+$6=self._controller();
+$ctx2.sendIdx["controller"]=1;
+return _st($6)._onVerticalDrag_(event);
+}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,1)})}));
+$ctx1.sendIdx["->"]=3;
+$2=smalltalk.HashedCollection._from_([$3,$4,$5]);
+_st($1)._draggable_($2);
+$ctx1.sendIdx["draggable:"]=1;
+$7=_st(self["@horizontalScrollbar"])._asJQuery();
+$9="containment".__minus_gt("parent");
+$ctx1.sendIdx["->"]=4;
+$10="axis".__minus_gt("x");
+$ctx1.sendIdx["->"]=5;
+$8=smalltalk.HashedCollection._from_([$9,$10,"drag".__minus_gt((function(event){
+return smalltalk.withContext(function($ctx2) {
+return _st(self._controller())._onHorizontalDrag_(event);
+}, function($ctx2) {$ctx2.fillBlock({event:event},$ctx1,2)})}))]);
+_st($7)._draggable_($8);
+self._updateScrollbars();
+return self}, function($ctx1) {$ctx1.fill(self,"setupScrollbars",{},smalltalk.MKScrollDecorator)})},
+args: [],
+source: "setupScrollbars\x0a\x09verticalScrollbar asJQuery draggable: #{\x0a\x09\x09'containment' -> 'parent'.\x0a\x09\x09'axis' -> 'y'.\x0a\x09\x09'drag' -> [ :event | self controller onVerticalDrag: event ]\x0a\x09}.\x0a\x09horizontalScrollbar asJQuery draggable: #{\x0a\x09\x09'containment' -> 'parent'.\x0a\x09\x09'axis' -> 'x'.\x0a\x09\x09'drag' -> [ :event | self controller onHorizontalDrag: event ]\x0a\x09}.\x0a\x09\x0a\x09self updateScrollbars",
+messageSends: ["draggable:", "asJQuery", "->", "onVerticalDrag:", "controller", "onHorizontalDrag:", "updateScrollbars"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "updateScrollbars",
+category: 'updating',
+fn: function (){
+var self=this;
+var width,height;
+return smalltalk.withContext(function($ctx1) { 
+var $1,$3,$2,$4,$5,$7,$6;
+$1=self._hasHorizontalOverflow();
+if(smalltalk.assert($1)){
+$3=self._scrollbarSize();
+$ctx1.sendIdx["scrollbarSize"]=1;
+$2=_st($3)._x();
+width=_st($2)._max_((10));
+$ctx1.sendIdx["max:"]=1;
+} else {
+width=(0);
+};
+$4=self._hasVerticalOverflow();
+if(smalltalk.assert($4)){
+height=_st(_st(self._scrollbarSize())._y())._max_((10));
+} else {
+height=(0);
+};
+$5=_st(self["@horizontalScrollbar"])._asJQuery();
+$ctx1.sendIdx["asJQuery"]=1;
+$7=_st(width)._asString();
+$ctx1.sendIdx["asString"]=1;
+$6=_st($7).__comma("%");
+$ctx1.sendIdx[","]=1;
+_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, '%'",
+messageSends: ["ifTrue:ifFalse:", "hasHorizontalOverflow", "max:", "x", "scrollbarSize", "hasVerticalOverflow", "y", "width:", "asJQuery", ",", "asString", "height:"],
+referencedClasses: []
+}),
+smalltalk.MKScrollDecorator);
+
+
+});

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


+ 8 - 8
js/Moka-Views.js

@@ -1432,7 +1432,7 @@ var $2,$3,$4,$1;
 $2=self["@listView"];
 if(($receiver = $2) == nil || $receiver == null){
 $3=_st($MKDropdownListView())._model_collectionAspect_selectionAspect_(self._model(),self._collectionAspect(),self._selectionAspect());
-_st($3)._width_("auto");
+_st($3)._width_(self._width());
 _st($3)._height_("auto");
 $4=_st($3)._yourself();
 self["@listView"]=$4;
@@ -1443,8 +1443,8 @@ $1=$2;
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"listView",{},smalltalk.MKDropdownView)})},
 args: [],
-source: "listView\x0a\x09^ listView ifNil: [\x0a\x09\x09listView := (MKDropdownListView \x09\x0a\x09\x09\x09model: self model\x0a\x09\x09\x09collectionAspect: self collectionAspect\x0a\x09\x09\x09selectionAspect: self selectionAspect)\x0a\x09\x09\x09\x09width: 'auto';\x0a\x09\x09\x09\x09height: 'auto';\x0a\x09\x09\x09\x09yourself ]",
-messageSends: ["ifNil:", "width:", "model:collectionAspect:selectionAspect:", "model", "collectionAspect", "selectionAspect", "height:", "yourself"],
+source: "listView\x0a\x09^ listView ifNil: [\x0a\x09\x09listView := (MKDropdownListView \x09\x0a\x09\x09\x09model: self model\x0a\x09\x09\x09collectionAspect: self collectionAspect\x0a\x09\x09\x09selectionAspect: self selectionAspect)\x0a\x09\x09\x09\x09width: self width;\x0a\x09\x09\x09\x09height: 'auto';\x0a\x09\x09\x09\x09yourself ]",
+messageSends: ["ifNil:", "width:", "model:collectionAspect:selectionAspect:", "model", "collectionAspect", "selectionAspect", "width", "height:", "yourself"],
 referencedClasses: ["MKDropdownListView"]
 }),
 smalltalk.MKDropdownView);
@@ -1466,11 +1466,11 @@ _st($3)._closeOnEnter_(true);
 _st($3)._closeOnClick_(true);
 _st($3)._addView_(self._listView());
 $4=$3;
-$6=self._position();
-$ctx1.sendIdx["position"]=1;
+$6=self._domPosition();
+$ctx1.sendIdx["domPosition"]=1;
 $5=_st($6)._x();
 _st($4)._left_($5);
-_st($3)._top_(_st(self._position())._y());
+_st($3)._top_(_st(self._domPosition())._y());
 _st($3)._height_((400));
 $7=_st($3)._yourself();
 self["@modalPaneView"]=$7;
@@ -1481,8 +1481,8 @@ $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 position x;\x0a\x09\x09\x09top: self position 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", "position", "top:", "y", "height:", "yourself"],
+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"]
 }),
 smalltalk.MKDropdownView);

+ 4 - 0
st/Kernel-Objects.st

@@ -1062,6 +1062,10 @@ e
 	<return Math.E;>
 !
 
+fromNumber: aNumber
+	<return Number(aNumber)>
+!
+
 pi
 	<return Math.PI>
 ! !

+ 26 - 1
st/Moka-Controllers.st

@@ -194,7 +194,7 @@ onKeyDown: anEvent
 	super onKeyDown: anEvent.
 	
 	anEvent keyCode = String cr asciiValue ifTrue: [
-		self selectItem: (self itemForTarget: anEvent target) ]
+		self selectItem: self view activeItem ]
 !
 
 onMouseMove: anEvent
@@ -285,3 +285,28 @@ isRepeating
 	^ delay notNil
 ! !
 
+MKController subclass: #MKScrollController
+	instanceVariableNames: ''
+	package: 'Moka-Controllers'!
+
+!MKScrollController methodsFor: 'accessing'!
+
+position
+	^ self view domDecoratedSize * self scrollPercent
+!
+
+scrollPercent
+	^ self view domScrollPosition / (self view domSize - self view domScrollbarSize)
+! !
+
+!MKScrollController methodsFor: 'actions'!
+
+onHorizontalDrag: anEvent
+	(self view decorated asJQuery get: 0) at: 'scrollLeft' put: self position x
+!
+
+onVerticalDrag: anEvent
+	console log: self position asString.
+	(self view decorated asJQuery get: 0) at: 'scrollTop' put: self position y
+! !
+

+ 88 - 9
st/Moka-Core.st

@@ -236,15 +236,6 @@ model: aModel
 	self observeModel
 !
 
-position
-	"Answer the position of the reciever in the page"
-	
-	^ root ifNotNil: [ 
-		|  offset |
-		offset := root asJQuery offset.
-		offset left @ offset top ]
-!
-
 tag
 	^ 'div'
 ! !
@@ -264,6 +255,12 @@ remove
 	root ifNotNil: [ root asJQuery remove ]
 ! !
 
+!MKView methodsFor: 'converting'!
+
+asJQuery
+	^ root asJQuery
+! !
+
 !MKView methodsFor: 'defaults'!
 
 defaultControllerClass
@@ -279,6 +276,20 @@ defaultLayout
 		yourself
 ! !
 
+!MKView methodsFor: 'dom'!
+
+domPosition
+	"Answer the position of the reciever in the page"
+	
+	| offset |
+	offset := self asJQuery offset.
+	^ offset left @ offset top
+!
+
+domSize
+	^ self asJQuery width @ self asJQuery height
+! !
+
 !MKView methodsFor: 'factory'!
 
 defaultController
@@ -287,34 +298,66 @@ defaultController
 
 !MKView methodsFor: 'layout'!
 
+bottom
+	^ self layout bottom
+!
+
 bottom: aNumber
 	self layout bottom: aNumber
 !
 
+centerX
+	^ self layout centerX
+!
+
 centerX: aNumber
 	self layout centerX: aNumber
 !
 
+centerY
+	^ self layout centerY
+!
+
 centerY: aNumber
 	self layout centerY: aNumber
 !
 
+height
+	^ self layout height
+!
+
 height: aNumber
 	self layout height: aNumber
 !
 
+left
+	^ self layout left
+!
+
 left: aNumber
 	self layout left: aNumber
 !
 
+right
+	^ self layout right
+!
+
 right: aNumber
 	self layout right: aNumber
 !
 
+top
+	^ self layout top
+!
+
 top: aNumber
 	self layout top: aNumber
 !
 
+width
+	^ self layout width
+!
+
 width: aNumber
 	self layout width: aNumber
 ! !
@@ -473,3 +516,39 @@ model: aModel aspect: aSelector
 		yourself
 ! !
 
+MKView subclass: #MKDecorator
+	instanceVariableNames: 'decorated'
+	package: 'Moka-Core'!
+!MKDecorator commentStamp!
+I am root class of the decorator pattern in Moka. 
+
+I am used to add rendering and/or behavior to other views.
+
+## API
+
+To decorate a view, use the class-side `#decorate:` method.!
+
+!MKDecorator methodsFor: 'accessing'!
+
+decorated
+	^ decorated
+!
+
+decorated: aView
+	decorated := aView
+! !
+
+!MKDecorator methodsFor: 'rendering'!
+
+renderContentOn: html
+	html with: self decorated
+! !
+
+!MKDecorator class methodsFor: 'instance creation'!
+
+decorate: aView
+	^ self new
+		decorated: aView;
+		yourself
+! !
+

+ 113 - 0
st/Moka-Decorators.st

@@ -0,0 +1,113 @@
+Smalltalk current createPackage: 'Moka-Decorators'!
+MKDecorator subclass: #MKScrollDecorator
+	instanceVariableNames: 'verticalScrollbar horizontalScrollbar'
+	package: 'Moka-Decorators'!
+
+!MKScrollDecorator methodsFor: 'accessing'!
+
+cssClass
+	^ super cssClass, ' mk_scroll'
+!
+
+scrollbarSize
+	| domSize overflow |
+	
+	domSize := self domSize.
+	overflow := self domOverflow.
+	^ ((domSize x / (overflow x + domSize x)) * 100) @ ((domSize y / (overflow y + domSize y) * 100))
+! !
+
+!MKScrollDecorator methodsFor: 'defaults'!
+
+defaultControllerClass
+	^ MKScrollController
+! !
+
+!MKScrollDecorator methodsFor: 'dom'!
+
+domDecoratedSize
+	| element |
+	element := self decorated asJQuery get: 0.
+	^ element scrollWidth @ element scrollHeight
+!
+
+domOverflow
+	| element |
+	element := self decorated asJQuery get: 0.
+	^ (element scrollWidth - element clientWidth) @ (element scrollHeight - element clientHeight)
+!
+
+domScrollPosition
+	^ horizontalScrollbar asJQuery position left @ verticalScrollbar asJQuery position top
+!
+
+domScrollbarSize
+	^ horizontalScrollbar asJQuery width @ verticalScrollbar asJQuery height
+! !
+
+!MKScrollDecorator methodsFor: 'private'!
+
+setupScrollbars
+	verticalScrollbar asJQuery draggable: #{
+		'containment' -> 'parent'.
+		'axis' -> 'y'.
+		'drag' -> [ :event | self controller onVerticalDrag: event ]
+	}.
+	horizontalScrollbar asJQuery draggable: #{
+		'containment' -> 'parent'.
+		'axis' -> 'x'.
+		'drag' -> [ :event | self controller onHorizontalDrag: event ]
+	}.
+	
+	self updateScrollbars
+! !
+
+!MKScrollDecorator methodsFor: 'rendering'!
+
+renderContentOn: html
+	html div 
+		class: 'mk_scroll_container';
+		with: [ super renderContentOn: html ].
+	
+	html div 
+		class: 'mk_scroll_rail vertical';
+		with: [
+			verticalScrollbar := html div
+				class: 'mk_scrollbar';
+				yourself ].
+	html div 
+		class: 'mk_scroll_rail horizontal';
+		with: [
+			horizontalScrollbar := html div
+				class: 'mk_scrollbar';
+				yourself ].
+	
+	self setupScrollbars
+! !
+
+!MKScrollDecorator methodsFor: 'testing'!
+
+hasHorizontalOverflow
+	^ self domOverflow x > 0
+!
+
+hasVerticalOverflow
+	^ self domOverflow y > 0
+! !
+
+!MKScrollDecorator methodsFor: 'updating'!
+
+updateScrollbars
+	| width height |
+	
+	width := self hasHorizontalOverflow
+		ifTrue: [ self scrollbarSize x max: 10 ]
+		ifFalse: [ 0 ].
+	height := self hasVerticalOverflow
+		ifTrue: [ self scrollbarSize y max: 10 ]
+		ifFalse: [ 0 ].
+	
+	horizontalScrollbar asJQuery width: width asString, '%'.
+	verticalScrollbar asJQuery height: height asString, '%'
+! !
+

+ 16 - 11
st/Moka-Examples.st

@@ -8,11 +8,11 @@ MKModel subclass: #MKClassesListBuilder
 build
 	MKPaneView new
 		height: 150;
-		addView: (
+		addView: ((MKScrollDecorator decorate:
 			(MKListView 	
 				model: MKClassesModel new
 				collectionAspect: #classes
-				selectionAspect: #selectedClass)
+				selectionAspect: #selectedClass))
 					left: 4;
 					top: 4;
 					bottom: 4;
@@ -65,10 +65,7 @@ Object subclass: #MKCounterBuilder
 build
 	| pane |
 	pane := MKPanelView new
-		top: 200;
-		width: 400;
 		borderRight: 1;
-		bottom: 0;
 		yourself.
 	
 	pane addView: ((MKHeadingView model: self counter aspect: #count)
@@ -88,6 +85,13 @@ build
 		top: 50;
 		left: 92;
 		yourself).
+	pane addView: ((MKDropdownView 
+		model: self counter
+		collectionAspect: #options
+		selectionAspect: #selectedOption)
+			left: 176;
+			top: 50;
+			yourself).
 	pane addView: ((MKInputView model: self counter aspect: #text)
 		top: 100;
 		left: 8;
@@ -136,11 +140,12 @@ build
 		left: 4;
 		top: 440;
 		yourself).
-		
-	pane render
-	"(MKButtonView model: self counter aspect: #decrease) 
-		label: 'Decrease';
-		render"
+	
+	(MKScrollDecorator decorate: pane)
+		top: 200;
+		width: 400;
+		bottom: 0;
+		render
 !
 
 counter
@@ -160,7 +165,7 @@ MKModel subclass: #MKCounterModel
 !MKCounterModel methodsFor: 'accessing'!
 
 options
-	^ Smalltalk current classes collect: [ :each | each name ]
+	^ #('foo' 'bar' 'baz')
 !
 
 selectedOption

+ 3 - 3
st/Moka-Views.st

@@ -546,7 +546,7 @@ listView
 			model: self model
 			collectionAspect: self collectionAspect
 			selectionAspect: self selectionAspect)
-				width: 'auto';
+				width: self width;
 				height: 'auto';
 				yourself ]
 !
@@ -558,8 +558,8 @@ modalPaneView
 			closeOnEnter: true;
 			closeOnClick: true;
 			addView: self listView;
-			left: self position x;
-			top: self position y;
+			left: self domPosition x;
+			top: self domPosition y;
 			"Max height of the list"
 			height: 400;
 			yourself ]

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