3

On my MVC project I have a View with 3 forms and one KO ViewModel.

On each form I have a reset button to clear its own fields.

I can't use input type="reset" because it doesn't update the observable values.

I can't either reset the View Model because that will reset all the observables of the View Model and I only need to reset the ones that are on the form that triggered the reset.

I can write 3 functions, one for each form and manually deleting each form fields like this:

this.onForm1Reset = function () {
    this.field1('');
    this.field2('');
    //etc..
}

this.onForm2Reset = function () {
    this.field3('');
    this.field4('');
    //etc..
}

this.onForm3Reset = function () {
    this.field5('');
    this.field6('');
    //etc..
}

But I'm looking for a more global and a shorter solution.

I made a lot of researches online but couldn't find a good solution for that.

Any help would be very much appreciated.

user3378165
  • 6,546
  • 17
  • 62
  • 101

4 Answers4

4

If you can group the forms' fields, you can use a with binding on each form to provide a different context to the form members, so the reset function (as a click handler) attached to a reset button will only be looking at the observables for its own form.

If you can't group the fields, a (less-preferred) possibility would be to run through the bound elements of the form containing the reset button, reset their values, and fire the change events for them. The commented-out code in my snippet would do that.

vm = {
    fieldset1: {
      field0: ko.observable(),
      field1: ko.observable()
    },
    fieldset2: {
      field2: ko.observable(),
      field3: ko.observable()
    },
    fieldset3: {
      field4: ko.observable(),
      field5: ko.observable()
    },
    reset: function(data) {
      for (var key in data) {
        if (data.hasOwnProperty(key)) {
          data[key]('');
        }
      }
    };

    ko.applyBindings(vm);

    /*
    $('body').on('click', '[type="reset"]', function() {
      $(this.parentNode).find('[data-bind*="value:"]').each((index, item) => {
        $(item).val('').change();
      });
    });
    */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form data-bind="with: fieldset1">
  <input data-bind="value:field0" />
  <input data-bind="value:field1" />
  <input type="reset" data-bind="click: $parent.reset" />
</form>

<form data-bind="with: fieldset2">
  <input data-bind="value:field2" />
  <input data-bind="value:field3" />
  <input type="reset" data-bind="click: $parent.reset" />
</form>

<form data-bind="with: fieldset3">
  <input data-bind="value:field4" />
  <input data-bind="value:field5" />
  <input type="reset" data-bind="click: $parent.reset" />
</form>
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Thank you for your answer, I have two questions: 1- I'm not sure if I can group the fields, how do I know if I can group them? (sorry if it's a stupid question..) 2- I have computed fields in each form, will that work with that? – user3378165 Aug 23 '16 at 18:30
  • If the structure of the viewModel is up to you, you can group them. If you are given a viewModel structure that you are prohibited from changing, you can't. (2) Yes, the computeds can use any observables in the entire system. – Roy J Aug 23 '16 at 19:26
  • Thank you, the viewModel is up to me so I can change it, I just tried it but for some reason I'm getting a syntax error near the lambda expression: `syntax error` any idea why? Thank you very much for your help! – user3378165 Aug 24 '16 at 06:31
  • @user3378165 If your browser doesn't handle lambda expression syntax, you can use `function` instead. Other than that, it's really hard to diagnose a syntax error without seeing the code. :) – Roy J Aug 24 '16 at 10:15
  • I saw online that it can be related to browsers but i'm getting the error in visual studio... ? – user3378165 Aug 24 '16 at 11:19
  • Try rewriting `(data) => {` as `function (data) {` – Roy J Aug 24 '16 at 11:42
  • Thanks, now I'm getting an error in the `of`: `const must be initialized`, is this a regular javascript code? seems like VS doesn't recognize it... – user3378165 Aug 24 '16 at 12:01
  • It's modern (ES6) JavaScript. You can use `var` instead of `const`. Hopefully `Object.keys` and `for...of` are supported. – Roy J Aug 24 '16 at 12:23
  • @user3378165 I have updated the code above to use older-style JavaScript for the `reset` function – Roy J Aug 24 '16 at 12:53
  • Thank you, it is recognized now. I have just one more question, if `field1` is a computed how do I access within `field1`- `field0`? `this.field0` is not working. Thank you! – user3378165 Aug 25 '16 at 06:52
  • @user3378165 Use your viewmodel variable (`vm` in my example) instead of `this`, like `vm.fieldset1.field1`. – Roy J Aug 25 '16 at 10:23
  • Thank you! It's basically working. I'm getting a `Uncaught Error: Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.` error but it doesn't bother the functionality.... – user3378165 Aug 25 '16 at 11:29
1

A more practical solution would be to contain your view-model in an observable, and re-create the model upon resetting:

function MyViewModel()
{
    this.field1 = ko.observable();
    this.field2 = ko.observable();

    // etc...
}

var model = {
    data: ko.observable(new MyViewModel()),
    reset: function() {
        model.data(new MyViewModel());
    }
};

ko.applyBindings(model);

And in your HTML:

<body data-bind="with: data">
    ...
    <button data-bind="click: $root.reset">Reset</button>
</body>

See Fiddle

Update (as per your comment):

Since you don't want to replace the entire view-model but only reset certain fields, I assume you'll have to introduce a reset() method to your model:

function MyViewModel()
{
    this.field1 = ko.observable();
    this.field2 = ko.observable();

    this.reset = function() {
        this.field1('');
        this.field2('');
    }.bind(this);
}

ko.applyBindings(new MyViewModel());

And in your HTML:

<button data-bind="click: reset">Reset</button>
haim770
  • 48,394
  • 7
  • 105
  • 133
  • Thank you for your answer but I don't think it fits my case, that will reset **the entire** `View Model` while I need to only reset a part of it. The `View Model` contains data of 3 forms and I need to reset each time one form.. – user3378165 Aug 23 '16 at 12:02
  • @user3378165, Then I guess you can treat the reset operation as a logical part of your view-model and that means you'll have to introduce a `reset()` method as part of your view-model that'll take care of resetting the desired fields. – haim770 Aug 23 '16 at 12:04
  • I'm not sure what you mean, would you be able to give me an example of how to do it? – user3378165 Aug 23 '16 at 12:08
  • Thanks for your update, my question is if I will have to do so for all my three forms and explicitly write all the fields of each form? Does that make scene to write a custom `Binding Handler`? – user3378165 Aug 23 '16 at 12:14
  • @user3378165, Custom binding-handler is for defining a new behavior against the DOM, not a model logic. Anyway, please update your question to reflect the multiple forms issue. – haim770 Aug 23 '16 at 12:16
  • Would you be able to explain me what the `.bind(this);` means? – user3378165 Aug 23 '16 at 14:32
  • @user3378165, Since Knockout may change the function context upon `click` binding, I had to explicitly `bind` it so that `this.field1/2` would still be referencing the correct fields. See http://stackoverflow.com/questions/2236747/use-of-the-javascript-bind-method – haim770 Aug 23 '16 at 15:07
  • Thank you for the explanation, now I understand it, thank you! – user3378165 Aug 24 '16 at 08:58
1

You might also want to consider splitting your ViewModel into one separate VM per Form(Group of fields).
Using this approach you coud reset a Form by recreating the respective ViewModel.

jbin
  • 166
  • 5
  • Thank you, I did so originally but I had to change it to one VM because the forms depends each other... (Some computed values based on observables of other form..) – user3378165 Aug 23 '16 at 12:55
  • 1
    Okay. So since subscribing to a observable in another VM should be more complicated than wrtiting separate reset methods, probably the latter is more practical, though I understand that this is not the most elegant way. – jbin Aug 23 '16 at 13:54
  • that's exactly my problem, I asked the question maybe someone thought of any brilliant idea... – user3378165 Aug 23 '16 at 14:29
0

Would something like this work? Where you have a function to empty observables? Depending on the click of reset?

this.emptyObservablesRestForm1 = function () {
   this.field1(false);
}


this.emptyObservablesRestForm2 = function () {
       this.field2(false);
    }
Aurora
  • 282
  • 3
  • 16
  • Thank you, I wrote in my question that this option is less optimized, I don't want to write 3 functions like this and to explicitly write all the fields to be reset. – user3378165 Aug 24 '16 at 05:52