Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is HTML5 form dirty? #160

Closed
kdawg1406 opened this issue Nov 21, 2015 · 16 comments

Comments

Projects
None yet
6 participants
@kdawg1406
Copy link

commented Nov 21, 2015

How do I determine if the HTML5 form is dirty from the Aurelia ViewModel? Angular had properties like $dirty, etc.

How has this been implemented in Aurelia?

I would like to expose an isDirty property on the ViewModel to enable/disable buttons, etc. based on form state.

Much appreciate guidance or added framework feature.

@jdanyow jdanyow added the question label Nov 30, 2015

@jdanyow

This comment has been minimized.

Copy link
Member

commented Nov 30, 2015

Aurelia doesn't have the equivalent of $dirty but we probably could build something using a BindingBehavior. In the meantime, take a look at how this has been implemented in the app-contacts sample app.

@kdawg1406

This comment has been minimized.

Copy link
Author

commented Nov 30, 2015

OK, thank you.

@kdawg1406

This comment has been minimized.

Copy link
Author

commented Nov 30, 2015

After taking a look, a binding behavior would provide real-time form state, as opposed to checking in deactivate.

I hope this can get added to this library or the validation library.

Best,

Karl

@kdawg1406

This comment has been minimized.

Copy link
Author

commented Dec 13, 2015

I've done some further investigation of BindingBehavior. Asking a developer to add a binding behavior to every binding on a form to track changes, is contrary to the bedrock principals of Aurelia; that being, not require framework goo or ceremony code for common scenarios.

I don't know much about the internals of Aurelia. Here are two possible solutions:

  1. Add a binding behavior to the element. Then any time a binding update occurs on any child controls, provide an event or property that can be checked. Possibly, not use the actual element, but create custom element that monitors all child controls bindings.
  2. Ask developers to implement get/set properties on all their objects, then when a property is changed, mark the entity object as dirty.

For LOB applications, isDirty is a main line scenario.

Thank, you,

Karl

@EisenbergEffect

This comment has been minimized.

Copy link
Member

commented Dec 13, 2015

I think the best way to do this would be with a custom attribute. That attribute can implement the created callback to gain access to the view. Using the metadata in the view it could find all the binding expressions within the form and use that to implement an isDirty functionality. I know @jdanyow has implemented something like this for validation with BreezeJS. Perhaps he can provide some additional insight.

@jdanyow

This comment has been minimized.

Copy link
Member

commented Dec 13, 2015

here's a quick and (cough) dirty implementation of the custom attribute:

dirty.js

import {bindingMode, customAttribute, inject} from 'aurelia-framework';

/**
  * A custom attribute that tracks whether users have interacted with two-way bound elements.
  */
@customAttribute('dirty', bindingMode.twoWay)
@inject(Element)
export class Dirty {
  view = null;
  bindings = null;
  element = null;

  constructor(element) {
    this.element = element;
  }

  created(view) {
    this.view = view;
  }

  bind() {
    // find all two-way bindings to elements within the element that has the 'dirty' attribute.
    this.bindings = this.view.bindings
      .filter(b => b.mode === bindingMode.twoWay && this.element.contains(b.target));
    // intercept each binding's updateSource method.
    let i = this.bindings.length;
    let self = this;
    while (i--) {
      let binding = this.bindings[i];
      binding.dirtyTrackedUpdateSource = binding.updateSource;
      binding.updateSource = function(newValue) {
        this.dirtyTrackedUpdateSource(newValue);
        if (!self.value) {
          self.value = true;
        }
      };
    }
  }

  unbind() {
    // disconnect the dirty tracking from each binding's updateSource method.
    let i = this.bindings.length;
    while (i--) {
      let binding = this.bindings[i];
      binding.updateSource = binding.dirtyTrackedUpdateSource;
      binding.dirtyTrackedUpdateSource = null;
    }
  }
}

You'd use it like this:

app.html

<require from="./dirty"></require>

<form dirty.bind="isDirty" submit.delegate="submit()">

  ... some inputs ...

  <button disabled.bind="!isDirty">Submit</button>
</form>

app.js

export class App {
  isDirty = false;

  // ... other properties ...

  submit() {
    // other submit logic...

    // reset isDirty state
    this.isDirty = false;
  }
}

Remaining work:

  1. If a user modifies a field and later undos their edit, the form will still be considered "dirty".
  2. Add ability to "revert" changes?
  3. Add ability to "accept" changes?
  4. Handle nested templates

NOTE: you'll need to wait for this week's release to start using this. The this.element.contains(b.target) line relies on some yet to be released binding engine changes.

@kdawg1406

This comment has been minimized.

Copy link
Author

commented Dec 13, 2015

Many thanks. Have a great day.

@KamBha

This comment has been minimized.

Copy link

commented Feb 16, 2016

@jdanyow
Thanks for this. What tools do we have available to access nested templates? Where can we find information on the binding object you mention above?

Thanks.

Kamal.

@stsje

This comment has been minimized.

Copy link

commented Feb 17, 2016

@timbell

This comment has been minimized.

Copy link

commented Feb 25, 2016

Just wanted to +1 this issue. In my case a boundValueChanged(name, newValue) callback on the view-model would be most helpful. I'm going to adapt the custom attribute code above to do the equivalent.

@EisenbergEffect

This comment has been minimized.

Copy link
Member

commented Feb 25, 2016

For a view model, if you define properties as bindable, you can implement a propertyChanged callback that will get invoked when any property changes. I'm not sure if that will work for you, but I wanted to mention it.

@EisenbergEffect

This comment has been minimized.

Copy link
Member

commented Feb 25, 2016

I think this would make a nice plugin for anyone who is interested in contributing. I'm sure @jdanyow would provide advice based on his example above.

@timbell

This comment has been minimized.

Copy link

commented Feb 25, 2016

@EisenbergEffect - thanks. I had come across propertyChanged but discounted it for some reason - I'll have another look.

@timbell

This comment has been minimized.

Copy link

commented Feb 25, 2016

@EisenbergEffect - my properties are members of a bound input object. Is there a way to make them @bindable without defining them explicitly in the view-model?

@KamBha

This comment has been minimized.

Copy link

commented Mar 25, 2016

Hi
I am trying to modify dirty to support dirty with embedded components.

You can see my work here:-

https://github.com/KamBha/aurelia-play

I have created a custom element called important-date to process a small part of my data model. I want to update the dirty code above to take into account my important-date bindings, but I don't see how to gain access to the bindings of the important-date.

How do I access sub component bindings?

@EisenbergEffect

This comment has been minimized.

Copy link
Member

commented May 16, 2016

Closing this for now since @jdanyow shows a solution above and there are community plugins for undo/redo binding behaviors as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.