Bladeren bron

Slight redesign, adding toggling optimization.

Blocks for trap:read: family are now `:model :html` instead of `:brush :model:`.

`trap:toggle:` added to show/hide only when changed to notNil/isNil.
This is an optimization to prevent frequent redraws.

Default-specifiable version of some methods added (old methods just
forward with a sane default):
 - `trapShow:default:`
 - `trap:toggle:ifNotPresent:`
Herbert Vojčík 11 jaren geleden
bovenliggende
commit
3d638cce67
7 gewijzigde bestanden met toevoegingen van 236 en 37 verwijderingen
  1. 3 3
      README.md
  2. 11 3
      js/Trapped-Demo.deploy.js
  3. 15 7
      js/Trapped-Demo.js
  4. 78 6
      js/Trapped-Frontend.deploy.js
  5. 99 12
      js/Trapped-Frontend.js
  6. 6 3
      st/Trapped-Demo.st
  7. 24 3
      st/Trapped-Frontend.st

+ 3 - 3
README.md

@@ -40,7 +40,7 @@ The `App` instance is put into global `AppVM` variable in `demo.html` initializa
 
 Trapped is pretty light: the view model wraps any object (via `payload:`,
 as seen in `App >> initialize`). The view is subclass of plain `Widget`, but inside it,
-uses of `trapShow:` (which itself uses `trap:read:`) and `trapDescend:` allows you
+uses of `trapShow:` (which itself uses `trap:read:`), `trap:toggle:` and `trapDescend:` allows you
 to bind data from view model.
 
 To see viewmodel->view update working, try
@@ -61,6 +61,6 @@ The `modify:do:` should be used for update since it changes as well as signals t
 It is planned that `read:do:` and `modify:do:` will guard the data by doing deep copies
 behind the scene against remembering and modifying out of sync.
 If you wish to, you can change the raw data you put into `payload:` by hand,
-but then be sure to call `AppVM changed: #('title')` or similar
-(you can do `AppVM changed: #()` to signal everything in `AppVM` has changed,
+but then be sure to call `AppVM dispatcher changed: #('title')` or similar
+(you can do `AppVM dispatcher changed: #()` to signal everything in `AppVM` has changed,
 but then everything depending upon it will redraw).

+ 11 - 3
js/Trapped-Demo.deploy.js

@@ -7,11 +7,15 @@ selector: "renderOn:",
 fn: function (html){
 var self=this;
 smalltalk.send(smalltalk.send(html,"_h2",[]),"_trapShow_",[["title"]]);
+smalltalk.send(smalltalk.send(html,"_div",[]),"_trap_toggle_ifNotPresent_",[["items"],(function(){
 smalltalk.send(smalltalk.send(html,"_p",[]),"_with_",[(function(){
-smalltalk.send(smalltalk.send(html,"_span",[]),"_trapShow_",[["items", smalltalk.symbolFor("size")]]);
+smalltalk.send(smalltalk.send(html,"_span",[]),"_trapShow_",[[smalltalk.symbolFor("size")]]);
 return smalltalk.send(html,"_with_",[" item(s)."]);
 })]);
-smalltalk.send(smalltalk.send(html,"_p",[]),"_trapShow_",[["items"]]);
+return smalltalk.send(smalltalk.send(html,"_p",[]),"_trapShow_",[[]]);
+}),(function(){
+return smalltalk.send(html,"_with_",["Loading ..."]);
+})]);
 return self}
 }),
 smalltalk.AppView);
@@ -110,7 +114,11 @@ selector: "initialize",
 fn: function (){
 var self=this;
 smalltalk.send(self,"_initialize",[],smalltalk.TrappedPlainModel);
-smalltalk.send(self,"_payload_",[smalltalk.HashedCollection._fromPairs_([smalltalk.send("items","__minus_gt",[["hello", "world"]]),smalltalk.send("title","__minus_gt",["To-Do List"])])]);
+smalltalk.send(self,"_payload_",[smalltalk.HashedCollection._fromPairs_([smalltalk.send("title","__minus_gt",["To-Do List"])])]);
+smalltalk.send((function(){
+smalltalk.send(smalltalk.send(self,"_payload",[]),"_at_put_",["items",["hello", "world"]]);
+return smalltalk.send(self,"_payload_",[smalltalk.send(self,"_payload",[])]);
+}),"_valueWithTimeout_",[(2000)]);
 return self}
 }),
 smalltalk.App);

+ 15 - 7
js/Trapped-Demo.js

@@ -8,15 +8,19 @@ category: 'rendering',
 fn: function (html){
 var self=this;
 smalltalk.send(smalltalk.send(html,"_h2",[]),"_trapShow_",[["title"]]);
+smalltalk.send(smalltalk.send(html,"_div",[]),"_trap_toggle_ifNotPresent_",[["items"],(function(){
 smalltalk.send(smalltalk.send(html,"_p",[]),"_with_",[(function(){
-smalltalk.send(smalltalk.send(html,"_span",[]),"_trapShow_",[["items", smalltalk.symbolFor("size")]]);
+smalltalk.send(smalltalk.send(html,"_span",[]),"_trapShow_",[[smalltalk.symbolFor("size")]]);
 return smalltalk.send(html,"_with_",[" item(s)."]);
 })]);
-smalltalk.send(smalltalk.send(html,"_p",[]),"_trapShow_",[["items"]]);
+return smalltalk.send(smalltalk.send(html,"_p",[]),"_trapShow_",[[]]);
+}),(function(){
+return smalltalk.send(html,"_with_",["Loading ..."]);
+})]);
 return self},
 args: ["html"],
-source: "renderOn: html\x0a\x09html h2 trapShow: #('title').\x0a    html p with: [ html span trapShow: #('items' #size). html with: ' item(s).' ].\x0a\x09html p trapShow: #('items')",
-messageSends: ["trapShow:", "h2", "with:", "span", "p"],
+source: "renderOn: html\x0a\x09html h2 trapShow: #('title').\x0a    html div trap: #('items') toggle: [\x0a        html p with: [ html span trapShow: #(#size). html with: ' item(s).' ].\x0a\x09\x09html p trapShow: #()\x0a    ] ifNotPresent: [ html with: 'Loading ...' ]",
+messageSends: ["trapShow:", "h2", "trap:toggle:ifNotPresent:", "with:", "span", "p", "div"],
 referencedClasses: []
 }),
 smalltalk.AppView);
@@ -146,11 +150,15 @@ category: 'initialization',
 fn: function (){
 var self=this;
 smalltalk.send(self,"_initialize",[],smalltalk.TrappedPlainModel);
-smalltalk.send(self,"_payload_",[smalltalk.HashedCollection._fromPairs_([smalltalk.send("items","__minus_gt",[["hello", "world"]]),smalltalk.send("title","__minus_gt",["To-Do List"])])]);
+smalltalk.send(self,"_payload_",[smalltalk.HashedCollection._fromPairs_([smalltalk.send("title","__minus_gt",["To-Do List"])])]);
+smalltalk.send((function(){
+smalltalk.send(smalltalk.send(self,"_payload",[]),"_at_put_",["items",["hello", "world"]]);
+return smalltalk.send(self,"_payload_",[smalltalk.send(self,"_payload",[])]);
+}),"_valueWithTimeout_",[(2000)]);
 return self},
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a\x09self payload: #{'items'->#('hello' 'world'). 'title' -> 'To-Do List'}",
-messageSends: ["initialize", "payload:", "->"],
+source: "initialize\x0a\x09super initialize.\x0a\x09self payload: #{'title' -> 'To-Do List'}.\x0a    [ self payload at: 'items' put: #('hello' 'world'). self payload: self payload ] valueWithTimeout: 2000\x0a",
+messageSends: ["initialize", "payload:", "->", "valueWithTimeout:", "at:put:", "payload"],
 referencedClasses: []
 }),
 smalltalk.App);

+ 78 - 6
js/Trapped-Frontend.deploy.js

@@ -67,6 +67,7 @@ selector: "payload:",
 fn: function (anObject){
 var self=this;
 self["@payload"]=anObject;
+smalltalk.send(smalltalk.send(self,"_dispatcher",[]),"_changed_",[[]]);
 return self}
 }),
 smalltalk.TrappedModelWrapper);
@@ -374,9 +375,62 @@ actual;
 model=smalltalk.send(smalltalk.send((smalltalk.Trapped || Trapped),"_current",[]),"_byName_",[smalltalk.send(actual,"_first",[])]);
 model;
 return smalltalk.send(model,"_watch_do_",[smalltalk.send(actual,"_allButFirst",[]),(function(data){
-return smalltalk.send(aBlock,"_value_value_",[self,data]);
+return smalltalk.send(actual,"_trapDescend_",[(function(){
+return smalltalk.send(self,"_with_",[(function(html){
+return smalltalk.send(aBlock,"_value_value_",[data,html]);
 })]);
 })]);
+})]);
+})]);
+return self}
+}),
+smalltalk.TagBrush);
+
+smalltalk.addMethod(
+"_trap_toggle_",
+smalltalk.method({
+selector: "trap:toggle:",
+fn: function (path,aBlock){
+var self=this;
+var $1,$2;
+smalltalk.send(self,"_trap_toggle_ifNotPresent_",[path,aBlock,(function(){
+$1=smalltalk.send(self,"_asJQuery",[]);
+smalltalk.send($1,"_empty",[]);
+$2=smalltalk.send($1,"_hide",[]);
+return $2;
+})]);
+return self}
+}),
+smalltalk.TagBrush);
+
+smalltalk.addMethod(
+"_trap_toggle_ifNotPresent_",
+smalltalk.method({
+selector: "trap:toggle:ifNotPresent:",
+fn: function (path,aBlock,anotherBlock){
+var self=this;
+var $1,$2,$3,$4;
+var shown;
+shown=nil;
+smalltalk.send(self,"_trap_read_",[path,(function(data,html){
+$1=smalltalk.send(shown,"__eq",[smalltalk.send(data,"_notNil",[])]);
+if(! smalltalk.assert($1)){
+shown=smalltalk.send(data,"_notNil",[]);
+shown;
+if(smalltalk.assert(shown)){
+$2=smalltalk.send(self,"_asJQuery",[]);
+smalltalk.send($2,"_empty",[]);
+$3=smalltalk.send($2,"_show",[]);
+$3;
+};
+if(smalltalk.assert(shown)){
+$4=aBlock;
+} else {
+$4=anotherBlock;
+};
+return smalltalk.send($4,"_value_value_",[data,html]);
+};
+})]);
 return self}
 }),
 smalltalk.TagBrush);
@@ -387,11 +441,29 @@ smalltalk.method({
 selector: "trapShow:",
 fn: function (path){
 var self=this;
-var $1;
-smalltalk.send(self,"_trap_read_",[path,(function(brush,model){
-smalltalk.send(brush,"_empty",[]);
-$1=smalltalk.send(brush,"_with_",[model]);
-return $1;
+smalltalk.send(self,"_trapShow_default_",[path,(function(){
+})]);
+return self}
+}),
+smalltalk.TagBrush);
+
+smalltalk.addMethod(
+"_trapShow_default_",
+smalltalk.method({
+selector: "trapShow:default:",
+fn: function (path,anObject){
+var self=this;
+var $1,$3,$2;
+smalltalk.send(self,"_trap_read_",[path,(function(model,html){
+$1=smalltalk.send(html,"_root",[]);
+smalltalk.send($1,"_empty",[]);
+if(($receiver = model) == nil || $receiver == undefined){
+$3=anObject;
+} else {
+$3=model;
+};
+$2=smalltalk.send($1,"_with_",[$3]);
+return $2;
 })]);
 return self}
 }),

+ 99 - 12
js/Trapped-Frontend.js

@@ -95,10 +95,11 @@ category: 'accessing',
 fn: function (anObject){
 var self=this;
 self["@payload"]=anObject;
+smalltalk.send(smalltalk.send(self,"_dispatcher",[]),"_changed_",[[]]);
 return self},
 args: ["anObject"],
-source: "payload: anObject\x0a\x09payload := anObject",
-messageSends: [],
+source: "payload: anObject\x0a\x09payload := anObject.\x0a    self dispatcher changed: #()",
+messageSends: ["changed:", "dispatcher"],
 referencedClasses: []
 }),
 smalltalk.TrappedModelWrapper);
@@ -497,17 +498,80 @@ actual;
 model=smalltalk.send(smalltalk.send((smalltalk.Trapped || Trapped),"_current",[]),"_byName_",[smalltalk.send(actual,"_first",[])]);
 model;
 return smalltalk.send(model,"_watch_do_",[smalltalk.send(actual,"_allButFirst",[]),(function(data){
-return smalltalk.send(aBlock,"_value_value_",[self,data]);
+return smalltalk.send(actual,"_trapDescend_",[(function(){
+return smalltalk.send(self,"_with_",[(function(html){
+return smalltalk.send(aBlock,"_value_value_",[data,html]);
+})]);
+})]);
 })]);
 })]);
 return self},
 args: ["path", "aBlock"],
-source: "trap: path read: aBlock\x0a\x09path trapDescend: [ | actual model |\x0a    \x09actual := Trapped path.\x0a        model := Trapped current byName: actual first.\x0a        model watch: actual allButFirst do: [ :data |\x0a        \x09aBlock value: self value: data\x0a    \x09]\x0a    ]",
-messageSends: ["trapDescend:", "path", "byName:", "first", "current", "watch:do:", "allButFirst", "value:value:"],
+source: "trap: path read: aBlock\x0a\x09path trapDescend: [ | actual model |\x0a    \x09actual := Trapped path.\x0a        model := Trapped current byName: actual first.\x0a        model watch: actual allButFirst do: [ :data |\x0a        \x09actual trapDescend: [ self with: [ :html | aBlock value: data value: html ] ]\x0a    \x09]\x0a    ]",
+messageSends: ["trapDescend:", "path", "byName:", "first", "current", "watch:do:", "allButFirst", "with:", "value:value:"],
 referencedClasses: ["Trapped"]
 }),
 smalltalk.TagBrush);
 
+smalltalk.addMethod(
+"_trap_toggle_",
+smalltalk.method({
+selector: "trap:toggle:",
+category: '*Trapped-Frontend',
+fn: function (path,aBlock){
+var self=this;
+var $1,$2;
+smalltalk.send(self,"_trap_toggle_ifNotPresent_",[path,aBlock,(function(){
+$1=smalltalk.send(self,"_asJQuery",[]);
+smalltalk.send($1,"_empty",[]);
+$2=smalltalk.send($1,"_hide",[]);
+return $2;
+})]);
+return self},
+args: ["path", "aBlock"],
+source: "trap: path toggle: aBlock\x0a    self trap: path toggle: aBlock ifNotPresent: [ self asJQuery empty; hide ]",
+messageSends: ["trap:toggle:ifNotPresent:", "empty", "asJQuery", "hide"],
+referencedClasses: []
+}),
+smalltalk.TagBrush);
+
+smalltalk.addMethod(
+"_trap_toggle_ifNotPresent_",
+smalltalk.method({
+selector: "trap:toggle:ifNotPresent:",
+category: '*Trapped-Frontend',
+fn: function (path,aBlock,anotherBlock){
+var self=this;
+var $1,$2,$3,$4;
+var shown;
+shown=nil;
+smalltalk.send(self,"_trap_read_",[path,(function(data,html){
+$1=smalltalk.send(shown,"__eq",[smalltalk.send(data,"_notNil",[])]);
+if(! smalltalk.assert($1)){
+shown=smalltalk.send(data,"_notNil",[]);
+shown;
+if(smalltalk.assert(shown)){
+$2=smalltalk.send(self,"_asJQuery",[]);
+smalltalk.send($2,"_empty",[]);
+$3=smalltalk.send($2,"_show",[]);
+$3;
+};
+if(smalltalk.assert(shown)){
+$4=aBlock;
+} else {
+$4=anotherBlock;
+};
+return smalltalk.send($4,"_value_value_",[data,html]);
+};
+})]);
+return self},
+args: ["path", "aBlock", "anotherBlock"],
+source: "trap: path toggle: aBlock ifNotPresent: anotherBlock\x0a    | shown |\x0a    shown := nil.\x0a    self trap: path read: [ :data : html |\x0a        shown = data notNil ifFalse: [\x0a            shown := data notNil.\x0a            shown ifTrue: [ self asJQuery empty; show ].\x0a            (shown ifTrue: [aBlock] ifFalse: [anotherBlock]) value: data value: html.\x0a        ]\x0a    ]",
+messageSends: ["trap:read:", "ifFalse:", "notNil", "ifTrue:", "empty", "asJQuery", "show", "value:value:", "ifTrue:ifFalse:", "="],
+referencedClasses: []
+}),
+smalltalk.TagBrush);
+
 smalltalk.addMethod(
 "_trapShow_",
 smalltalk.method({
@@ -515,16 +579,39 @@ selector: "trapShow:",
 category: '*Trapped-Frontend',
 fn: function (path){
 var self=this;
-var $1;
-smalltalk.send(self,"_trap_read_",[path,(function(brush,model){
-smalltalk.send(brush,"_empty",[]);
-$1=smalltalk.send(brush,"_with_",[model]);
-return $1;
+smalltalk.send(self,"_trapShow_default_",[path,(function(){
 })]);
 return self},
 args: ["path"],
-source: "trapShow: path\x0a\x09self trap: path read: [ :brush :model | brush empty; with: model ]",
-messageSends: ["trap:read:", "empty", "with:"],
+source: "trapShow: path\x0a\x09self trapShow: path default: []",
+messageSends: ["trapShow:default:"],
+referencedClasses: []
+}),
+smalltalk.TagBrush);
+
+smalltalk.addMethod(
+"_trapShow_default_",
+smalltalk.method({
+selector: "trapShow:default:",
+category: '*Trapped-Frontend',
+fn: function (path,anObject){
+var self=this;
+var $1,$3,$2;
+smalltalk.send(self,"_trap_read_",[path,(function(model,html){
+$1=smalltalk.send(html,"_root",[]);
+smalltalk.send($1,"_empty",[]);
+if(($receiver = model) == nil || $receiver == undefined){
+$3=anObject;
+} else {
+$3=model;
+};
+$2=smalltalk.send($1,"_with_",[$3]);
+return $2;
+})]);
+return self},
+args: ["path", "anObject"],
+source: "trapShow: path default: anObject\x0a\x09self trap: path read: [ :model :html | html root empty; with: (model ifNil: [anObject]) ]",
+messageSends: ["trap:read:", "empty", "root", "with:", "ifNil:"],
 referencedClasses: []
 }),
 smalltalk.TagBrush);

+ 6 - 3
st/Trapped-Demo.st

@@ -7,8 +7,10 @@ Widget subclass: #AppView
 
 renderOn: html
 	html h2 trapShow: #('title').
-    html p with: [ html span trapShow: #('items' #size). html with: ' item(s).' ].
-	html p trapShow: #('items')
+    html div trap: #('items') toggle: [
+        html p with: [ html span trapShow: #(#size). html with: ' item(s).' ].
+		html p trapShow: #()
+    ] ifNotPresent: [ html with: 'Loading ...' ]
 ! !
 
 TrappedDispatcher subclass: #TrappedDumbDispatcher
@@ -68,6 +70,7 @@ TrappedPlainModel subclass: #App
 
 initialize
 	super initialize.
-	self payload: #{'items'->#('hello' 'world'). 'title' -> 'To-Do List'}
+	self payload: #{'title' -> 'To-Do List'}.
+    [ self payload at: 'items' put: #('hello' 'world'). self payload: self payload ] valueWithTimeout: 2000
 ! !
 

+ 24 - 3
st/Trapped-Frontend.st

@@ -46,7 +46,8 @@ payload
 !
 
 payload: anObject
-	payload := anObject
+	payload := anObject.
+    self dispatcher changed: #()
 ! !
 
 !TrappedModelWrapper methodsFor: 'action'!
@@ -190,12 +191,32 @@ trap: path read: aBlock
     	actual := Trapped path.
         model := Trapped current byName: actual first.
         model watch: actual allButFirst do: [ :data |
-        	aBlock value: self value: data
+        	actual trapDescend: [ self with: [ :html | aBlock value: data value: html ] ]
     	]
     ]
 !
 
+trap: path toggle: aBlock
+    self trap: path toggle: aBlock ifNotPresent: [ self asJQuery empty; hide ]
+!
+
+trap: path toggle: aBlock ifNotPresent: anotherBlock
+    | shown |
+    shown := nil.
+    self trap: path read: [ :data : html |
+        shown = data notNil ifFalse: [
+            shown := data notNil.
+            shown ifTrue: [ self asJQuery empty; show ].
+            (shown ifTrue: [aBlock] ifFalse: [anotherBlock]) value: data value: html.
+        ]
+    ]
+!
+
 trapShow: path
-	self trap: path read: [ :brush :model | brush empty; with: model ]
+	self trapShow: path default: []
+!
+
+trapShow: path default: anObject
+	self trap: path read: [ :model :html | html root empty; with: (model ifNil: [anObject]) ]
 ! !