Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.javascript > #37421 > unrolled thread

A very small MVC prototype

Started byluserdroog <luser.droog@gmail.com>
First post2019-09-26 19:01 -0700
Last post2019-09-30 03:21 -0700
Articles 7 — 3 participants

Back to article view | Back to comp.lang.javascript


Contents

  A very small MVC prototype luserdroog <luser.droog@gmail.com> - 2019-09-26 19:01 -0700
    Re: A very small MVC prototype "Michael Haufe (TNO)" <tno@thenewobjective.com> - 2019-09-26 19:54 -0700
    Re: A very small MVC prototype Julio Di Egidio <julio@diegidio.name> - 2019-09-27 07:11 -0700
      Re: A very small MVC prototype luserdroog <luser.droog@gmail.com> - 2019-09-27 15:03 -0700
        Re: A very small MVC prototype Julio Di Egidio <julio@diegidio.name> - 2019-09-27 17:04 -0700
          Re: A very small MVC prototype luserdroog <luser.droog@gmail.com> - 2019-09-28 08:39 -0700
            Re: A very small MVC prototype Julio Di Egidio <julio@diegidio.name> - 2019-09-30 03:21 -0700

#37421 — A very small MVC prototype

Fromluserdroog <luser.droog@gmail.com>
Date2019-09-26 19:01 -0700
SubjectA very small MVC prototype
Message-ID<3c76a24e-3d87-49f1-acec-555593733973@googlegroups.com>
After a night and a day of reading and hacking, I've got a 
smaller and simpler version working with no jQuery in sight.

Does this look any better? I've tried to keep it simple. 
I didn't see an easy way to make my model objects accept
an event listener. So I just use functions. I suppose the
"view" part of MVC isn't fully manifested here. It's kindof
half in the html, half in the controller, and another half
in the model. Maybe I'll do something about that.



$ cat mvc.html
<!doctype html>
<html lang=en><meta charset=utf-8>

<style>
</style>

<script>
window.onload = ()=>{
  // user via view triggers model changes
  attach( qs('#b_f'),'click', () => Doodle.add('F') );
  attach( qs('#b_b'),'click', () => Doodle.add('B') );
  attach( qs('#b_l'),'click', () => Doodle.add('L') );
  attach( qs('#b_r'),'click', () => Doodle.add('R') );

  //model changes trigger view updates
  Doodle.change = () => {
    qs(".info").innerHTML = Doodle.data.toString();
    Doodle.render();
  };

  Doodle.render();
};

let Turtle = ()=>{ return { x: 10, y: 10, a: 0 }; };

let Doodle = {
  data: [],
  add: x => {Doodle.data.push(x); (Doodle.change||nop)() },

  render: () => {
    let cx = qs("canvas").getContext('2d');
    cx.clearRect( 0, 0, cx.canvas.width, cx.canvas.height );
    cx.beginPath();
    let ts = scan(
        Doodle.data,
        (t,e) => { return {
            x: (t.x + (e=='F'?1:e=='B'?-1:0)*20*Math.cos(rad(t.a))),
            y: (t.y + (e=='F'?1:e=='B'?-1:0)*20*Math.sin(rad(t.a))),
            a: (t.a + (e=='R'?1:e=='L'?-1:0)*90)
        }; },
        Turtle()
    );
    let ps = ts.map(t=>[t.x, t.y]);
    cx.moveTo(...ps[0]);
    ps.slice(1).forEach(p => cx.lineTo(...p));
    cx.stroke();
    let t = ts[ ts.length - 1 ];
    cx.fillRect( t.x -2, t.y -2, 4, 4 );
  },
};

//like array.reduce() but returns array of intermediate results
function scan(){
    let a = arguments[0],
        f = arguments[1],
        s = arguments[2] || {},
        z = [],
        i = 0;
    if( s !== {} ){
        z.push( s );
    } else {
        z.push( a[0] );
        ++i;
    }
    for(; i < a.length; ++i ){
        let last = z[ z.length - 1 ];
        z.push( f( last, a[i] ) );
    }
    return z;
};

let rad = a => a * (Math.PI / 180);
let nop = () => 0;
let attach = (x,y,z) => x.addEventListener( y, z );
let qs = (sel,ctx) => (ctx || document).querySelector( sel );
let qsa = (sel,ctx) => Array.from( (ctx || document).querySelectorAll( sel ) );
</script>

<main>
  <section class=sketch> <canvas> </section>
  <section class=commands>
    <button id=b_f >F</button>
    <button id=b_b >B</button>
    <button id=b_l >L</button>
    <button id=b_r >R</button>
  </section>
  <section class=info>
  </section>
  

[toc] | [next] | [standalone]


#37422

From"Michael Haufe (TNO)" <tno@thenewobjective.com>
Date2019-09-26 19:54 -0700
Message-ID<f04fbd5d-8b25-4dea-94ec-8475e0ad613e@googlegroups.com>
In reply to#37421
On Thursday, September 26, 2019 at 9:01:59 PM UTC-5, luserdroog wrote:
> After a night and a day of reading and hacking, I've got a 
> smaller and simpler version working with no jQuery in sight.
> 
> Does this look any better? I've tried to keep it simple. 
> I didn't see an easy way to make my model objects accept
> an event listener. So I just use functions. I suppose the
> "view" part of MVC isn't fully manifested here. It's kindof
> half in the html, half in the controller, and another half
> in the model. Maybe I'll do something about that.
> 
> 
> 
> $ cat mvc.html
> <!doctype html>
> <html lang=en><meta charset=utf-8>
> 
> <style>
> </style>
> 
> <script>
> window.onload = ()=>{
>   // user via view triggers model changes
>   attach( qs('#b_f'),'click', () => Doodle.add('F') );
>   attach( qs('#b_b'),'click', () => Doodle.add('B') );
>   attach( qs('#b_l'),'click', () => Doodle.add('L') );
>   attach( qs('#b_r'),'click', () => Doodle.add('R') );
> 
>   //model changes trigger view updates
>   Doodle.change = () => {
>     qs(".info").innerHTML = Doodle.data.toString();
>     Doodle.render();
>   };
> 
>   Doodle.render();
> };
> 
> let Turtle = ()=>{ return { x: 10, y: 10, a: 0 }; };
> 
> let Doodle = {
>   data: [],
>   add: x => {Doodle.data.push(x); (Doodle.change||nop)() },
> 
>   render: () => {
>     let cx = qs("canvas").getContext('2d');
>     cx.clearRect( 0, 0, cx.canvas.width, cx.canvas.height );
>     cx.beginPath();
>     let ts = scan(
>         Doodle.data,
>         (t,e) => { return {
>             x: (t.x + (e=='F'?1:e=='B'?-1:0)*20*Math.cos(rad(t.a))),
>             y: (t.y + (e=='F'?1:e=='B'?-1:0)*20*Math.sin(rad(t.a))),
>             a: (t.a + (e=='R'?1:e=='L'?-1:0)*90)
>         }; },
>         Turtle()
>     );
>     let ps = ts.map(t=>[t.x, t.y]);
>     cx.moveTo(...ps[0]);
>     ps.slice(1).forEach(p => cx.lineTo(...p));
>     cx.stroke();
>     let t = ts[ ts.length - 1 ];
>     cx.fillRect( t.x -2, t.y -2, 4, 4 );
>   },
> };
> 
> //like array.reduce() but returns array of intermediate results
> function scan(){
>     let a = arguments[0],
>         f = arguments[1],
>         s = arguments[2] || {},
>         z = [],
>         i = 0;
>     if( s !== {} ){
>         z.push( s );
>     } else {
>         z.push( a[0] );
>         ++i;
>     }
>     for(; i < a.length; ++i ){
>         let last = z[ z.length - 1 ];
>         z.push( f( last, a[i] ) );
>     }
>     return z;
> };
> 
> let rad = a => a * (Math.PI / 180);
> let nop = () => 0;
> let attach = (x,y,z) => x.addEventListener( y, z );
> let qs = (sel,ctx) => (ctx || document).querySelector( sel );
> let qsa = (sel,ctx) => Array.from( (ctx || document).querySelectorAll( sel ) );
> </script>
> 
> <main>
>   <section class=sketch> <canvas> </section>
>   <section class=commands>
>     <button id=b_f >F</button>
>     <button id=b_b >B</button>
>     <button id=b_l >L</button>
>     <button id=b_r >R</button>
>   </section>
>   <section class=info>
>   </section>

Yes, I think this looks significantly better. Still plenty of room for improvement. I'll only give a partial response right now due to time constraints.

# EVENTS

window.onload will not execute until every resource on the page is done loading. This may sound reasonable at first but if you have resources like images, you'll have to wait for them all to complete first before your code will run. It's faster and more appropriate in most cases to use the DOMContentLoaded event instead.

Also, when you assign to window.onload, or any other event, you replace any existing event handler as well as restrict yourself to a single event listener. So in general you should register events like this:

document.addEventListener('eventName', fnEventHandler)

for example:

document.addEventListener('DOMContentLoaded', (e) => {
	...
});

# MVC

Instead of a fuller code review, I'm going to point towards how to approach transformation into MVC. 

Identify the Model(s) of your application. What are the entities, the "nouns" of what your manipulating?

You have the concept of Doodle and Turtle. These are model candidates.

Views: You have a canvas, commands, and the container for them both.

Controllers: events on the respective views captured by the associated controllers.

Perhaps this example would help for conceptual comparison:

<http://peter.michaux.ca/examples/clocks/mvc-clock>

[toc] | [prev] | [next] | [standalone]


#37434

FromJulio Di Egidio <julio@diegidio.name>
Date2019-09-27 07:11 -0700
Message-ID<d77256a5-7edc-4fc5-8231-dcbeb9bce755@googlegroups.com>
In reply to#37421
On Friday, 27 September 2019 04:01:59 UTC+2, luserdroog  wrote:

> I suppose the "view" part of MVC isn't fully manifested here.

MVC is the most complex of those patterns and way overkill in most cases:
MVVM is usually just enough and indeed is what you'd find most commonly
implemented for interfaces with low to medium-high complexity.  MVC is
also most difficult to pin down due to literally decades of misguidance and
speculations on these topics, which adds to the learning curve intrinsic to
understanding what patters even are and are for.

At a more technical level: learn *Knockout.js*, that's the next level...

Julio

[toc] | [prev] | [next] | [standalone]


#37442

Fromluserdroog <luser.droog@gmail.com>
Date2019-09-27 15:03 -0700
Message-ID<2bf28d0b-1df2-4909-9885-3380712cb100@googlegroups.com>
In reply to#37434
On Friday, September 27, 2019 at 9:11:44 AM UTC-5, Julio Di Egidio wrote:
> On Friday, 27 September 2019 04:01:59 UTC+2, luserdroog  wrote:
> 
> > I suppose the "view" part of MVC isn't fully manifested here.
> 
> MVC is the most complex of those patterns and way overkill in most cases:
> MVVM is usually just enough and indeed is what you'd find most commonly
> implemented for interfaces with low to medium-high complexity.  MVC is
> also most difficult to pin down due to literally decades of misguidance and
> speculations on these topics, which adds to the learning curve intrinsic to
> understanding what patters even are and are for.
> 
> At a more technical level: learn *Knockout.js*, that's the next level...
> 

Thanks, I think I'm ok with having the view portion a little vague.
But I now think that it is a bad thing to have 'view' pieces inside
the model. The render() function in my little example is badly located.

[toc] | [prev] | [next] | [standalone]


#37444

FromJulio Di Egidio <julio@diegidio.name>
Date2019-09-27 17:04 -0700
Message-ID<c59d2461-329f-4193-8095-d2b2973d62ce@googlegroups.com>
In reply to#37442
On Saturday, 28 September 2019 00:03:25 UTC+2, luserdroog  wrote:
> On Friday, September 27, 2019 at 9:11:44 AM UTC-5, Julio Di Egidio wrote:
> > On Friday, 27 September 2019 04:01:59 UTC+2, luserdroog  wrote:
> > 
> > > I suppose the "view" part of MVC isn't fully manifested here.
> > 
> > MVC is the most complex of those patterns and way overkill in most cases:
> > MVVM is usually just enough and indeed is what you'd find most commonly
> > implemented for interfaces with low to medium-high complexity.  MVC is
> > also most difficult to pin down due to literally decades of misguidance and
> > speculations on these topics, which adds to the learning curve intrinsic to
> > understanding what patters even are and are for.
> > 
> > At a more technical level: learn *Knockout.js*, that's the next level...
> 
> Thanks, I think I'm ok with having the view portion a little vague.

It needn't be 100% of a given pattern, in fact the other way round (but this
is a long story, about patters, and I won't get into it), but it should never
be "vague"...  Indeed, I was reading the code and (FWIW) it's immediately quite
painful: to begin with, please do not use abbreviated/short variable names
(unless for the obvious cases).  Code must be readable (not writable), that's
indeed the very first principle and code quality indicator.

> But I now think that it is a bad thing to have 'view' pieces inside
> the model. The render() function in my little example is badly located.

I think everything in your example is badly located, but even more I wander why
you have called it "MVC prototype" at all, there is just no trace of a model,
view and controller anywhere... :)

Maybe keep in mind that MVC, as the other design patterns, is about a clear
partitioning of code and responsibilities, as well as about a specific flow of
messages.

Julio

[toc] | [prev] | [next] | [standalone]


#37449

Fromluserdroog <luser.droog@gmail.com>
Date2019-09-28 08:39 -0700
Message-ID<3af075d1-3eae-4e83-b902-586798a99b78@googlegroups.com>
In reply to#37444
On Friday, September 27, 2019 at 7:04:56 PM UTC-5, Julio Di Egidio wrote:
> On Saturday, 28 September 2019 00:03:25 UTC+2, luserdroog  wrote:
> > But I now think that it is a bad thing to have 'view' pieces inside
> > the model. The render() function in my little example is badly located.
> 
> I think everything in your example is badly located, but even more I wander why
> you have called it "MVC prototype" at all, there is just no trace of a model,
> view and controller anywhere... :)
> 

Sigh. That's another bad habit of mine. I wrote a post to comp.lang.c
in the past year called "Simulating Monads, Lambdas, and Closures".
And it didn't really exhibit any of those things in any recognizable way.
But I hope I continue to be *brave in the attempt.*

The "model" part of MVC seems pretty easy to implement, with plain old
data structures. The "controller" appears to me to be all the hooking 
of event handlers between the html components and the model, and the 
orchestrating of updates to the view in responses to changes in the model.

I'm not yet seeing a nice way to gather up the "view" into one unit.
Maybe two parts better fits my current mental model. If the Model and
View are "objects", then the Controller seems to me to govern the two
channels of information flow between them.

       Control
       -------
         -->
  Model        View
         <--

I think I can make the code reflect this sort of structure. If any of
this seems misguided, I'm amendable to correction.

[toc] | [prev] | [next] | [standalone]


#37480

FromJulio Di Egidio <julio@diegidio.name>
Date2019-09-30 03:21 -0700
Message-ID<f59673d9-a3d2-4e01-b3fc-bff23089ed4e@googlegroups.com>
In reply to#37449
On Saturday, 28 September 2019 17:40:04 UTC+2, luserdroog  wrote:
> On Friday, September 27, 2019 at 7:04:56 PM UTC-5, Julio Di Egidio wrote:
> > On Saturday, 28 September 2019 00:03:25 UTC+2, luserdroog  wrote:
<snip>
> > I think everything in your example is badly located, but even more I wander why
> > you have called it "MVC prototype" at all, there is just no trace of a model,
> > view and controller anywhere... :)
> 
> Sigh. That's another bad habit of mine. I wrote a post to comp.lang.c
> in the past year called "Simulating Monads, Lambdas, and Closures".
> And it didn't really exhibit any of those things in any recognizable way.
> But I hope I continue to be *brave in the attempt.*

And I hope for you that it's an attempt at improving yourself, otherwise
it's just yet another exercise in arrogance.

Indeed, as the typical average computer scientist, you have not even noticed
that this is just not that class: so take note, Computer Science is an applied
mathematics, SE is about software production and it's an engineering.  Your
code is a *liability* in production: to begin with, unreadable means buggy
and unmaintainable.

> The "model" part of MVC seems pretty easy to implement, with plain old
> data structures.

And I told you it's not just about the code partitioning, it's also and even
more importantly about the specific flow of messages: who *knows* about whom.

Indeed, for a starter, one thing may be worth appreciating: these patterns are
all descendants of a more fundamental one, inversion of control, whose essential
purpose is that of breaking circular dependencies...

HTH,

Julio

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.javascript


csiph-web