Form actions with viewmodel


#1

I have two separate issues. I’m trying to do a template event with viewmodel instead, but I don’t know what’s the proper approach. The template event is a submit action on an input form, but it doesn’t seem that there’s an equivalent with viewmodel.

'submit .new-discuss'(event) {
  // Prevent default browser form submit
  event.preventDefault();

  // Get value from form element
  const target = event.target;
  const text = target.text.value;

  //Determine which area to insert
  var area = userArea();

  // Insert a discussion into discuss
  Meteor.call('discuss.insert', text, area);

  // Clear form
  target.text.value = '';
}

The other is that I need a reactive variable such that the window is responsive with height. I have tried to use flex, but it doesn’t seem to be working at all. Therefore, I have tried to apply the following (with viewmodel), but it only renders when the window is refreshed. I don’t understand why it’s not reactive.

areaHeight() {
  return $(window).height() + "px";
}

#2

The first one is solved by adding a submit binding to the form or a click binding to the submit button (in that case make it of type button so you don’t have to use preventDefault).

The second one happens because height isn’t reactive. You need to use a signal on the window or document resize. Look up Signals in the ViewModel docs and Google for an event that tells you the window height when it changes.

btw, how’s ViewModel working for you?


#3

I tried doing that with the enter binding, but it doesn’t seem to work.

<input {{b "value: Event.address, enter: editEvent"}} type="text" name="address" id="form-address" />

Instead it’s redirecting to the following address:

http: events?address=none&description=none

And it corresponds with the following function:

editEvent() {
 if(this.edit()) {
   var updateEvent = this.Event();

   updateEvent.address = document.getElementById("form-address").value;
   updateEvent.description = document.getElementById("form-description").value;

   Meteor.call('update.event', updateEvent._id, updateEvent);
   update();

   this.edit(false);
}

And oh my goodness yes! It’s quite the experience. Each moment is mesmerizing and sensuous. It’s like nothing else. :wink:


#4

I was thinking more like <form {{b "submit: doSomething"}}> but in your case:

<input {{b "value: Event.address, enter: editEvent"}} type="text" name="address" id="form-address" />
var updateEvent = this.Event();
updateEvent.address = document.getElementById("form-address").value;
Meteor.call('update.event', updateEvent._id, updateEvent);

That’s a lot of work. The event should be in its own template/viewmodel so you can do something like:

<input {{b "value: address, enter: updateEvent"}} type="text" />

And on updateEvent:

Meteor.call('update.event', this._id(), this.data());

And oh my goodness yes! It’s quite the experience. Each moment is mesmerizing and sensuous. It’s like nothing else. :wink:

hmmm, okay… I guess?


#5

That was it. I used <form {{b "submit: updateEvent"}}> for the form. Then included the following in the function. I can’t do as you suggested because the save button isn’t within the form.

updateEvent(event) { 
  event.preventDefault(); 
}

And yeah, I applied viewmodel and it reduced the amount of code by about half. I think my issue is that viewmodel is meant for somebody more advanced and as a beginner, I have to infer a lot from your documentation. I still don’t understand how to use the signals from the examples you provided.

I found resize as an event function that should probably be used, but I don’t know how that is used with the target property. The object that was return with the following didn’t contain anything.

ViewModel.signal({
  window: {
    height: {
      target: document,
      event: 'resize'
    }
  }
});

#6

the save button isn’t within the form

Interesting. I’ll think about that one. My brain is fried right now but there has to be clean way to handle that.

I think my issue is that viewmodel is meant for somebody more advanced and as a beginner, I have to infer a lot from your documentation.

Now that hurts =(
What do you think would help you “get it right”?

As for signals, document doesn’t have a resize event. So the target for resize is window, not document.

So you prepare this signal:

ViewModel.signal({
  window: {
    windowSize: {
      target: window,
      event: 'resize',
    }
  }
});

How do you use it? Let’s say you want to display the window.innerHeight (basically the portion of the page you see) and a div/box that is half the window.innerHeight. You can do the following:

<body>
  <h1 {{b "text: 'Height: ' + height"}}></h1>
  <div
    {{b "style: { height: height }"}} 
    style="border: 1px solid black; width: 200px"></div>
</body>
Template.body.viewmodel({
  signal: 'window',
  height() {
    // the resize event object doesn't have anything useful
    // we just want to depend on it.
    this.windowSize.depend();
    return window.innerHeight / 2;
  }
});

#7

Yeah, I would have not known how to do that based on the documentation. I think viewmodel is really awesome, but I don’t understand how the different object respond to the properties. I seems to make sense that the signal is updated when the defined event happens, but how that relates to the target and the objects doesn’t make sense to me.

Why return the window object (and its unknown properties) when the windowSize object is the one being updated? And how to you know what’s the target property (window instead of document)? It may be because I don’t understand some more elements of javascript, but I wouldn’t know where to begin to understand those details unless specified by you.


#8

I didn’t know the answer either. Here’s what I did:

  • I knew I wanted a property to be “reactive” based on the size of the screen. “Reactive” as in “the value of the property should change as the screen size changes”.
  • I googled “javascript screen resize event”.
  • The 2nd result was “JavaScript window resize event - Stack Overflow”
  • One of the answers has the following example:
window.addEventListener('resize', function(event){
  // do stuff here
});
  • From that I assumed I needed the resize event of the window object.
  • That told me I needed a signal:
ViewModel.signal({
  windowSignal: {
    windowSize: {
      target: window,
      event: 'resize',
    }
  }
});

The names windowSignal and windowSize are completely arbitrary. What matters is that the target is window and the event is resize.

  • I wanted the height so my first thought was that the event will give me that information.
  • I googled different terms but didn’t get the properties of the resize event object.
  • I was sure I could get the information if I googled a bit more but it was easier to just debug the thing, so I put the windowSize on an autorun so it writes to the console whenever the window resized:
Template.body.viewmodel({
  signal: 'windowSignal',
  autorun() {
    console.log(this.windowSize());
  }
});
  • I didn’t see anything useful on the event object (I wanted the height).
  • I googled “javascript screen height” and the 4th result was “Javascript: How to determine the screen height visible (i.e., removing”
  • One answer simply said window.innerHeight
  • That looked promising so I googled “javascript window.innerHeight” and the first result was “Window.innerHeight - Web APIs | MDN”
  • Read the docs and it was exactly what I wanted.
  • By then I knew I needed to depend on the resize event of the window but return the innerHeight.
  • And that’s how I ended up with:
Template.body.viewmodel({
  signal: 'windowSignal',
  height() {
    // the resize event object doesn't have anything useful
    // we just want to depend on it.
    this.windowSize.depend();
    return window.innerHeight / 2;
  }
});

The documentation explains the .depend(): https://viewmodel.org/docs/viewmodels#properties

.depend()
Causes the current function to depend on the property without actually invoking it.

In practice it’s just a clearer way of doing:

this.windowSize(); // Depends on the windowSize but ??? (you're not doing anything with the value)
  • Then it was just a matter of showing/using the height property on the template:
<body>
  <h1 {{b "text: 'Height: ' + height"}}></h1>
  <div
    {{b "style: { height: height }"}} 
    style="border: 1px solid black; width: 200px"></div>
</body>

#9

+1

… and now we all know how @manuel’s mind works :wink:


#10

Thank you again, I appreciate your help.