最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Aurelia binding - back to view model on button click - Stack Overflow

programmeradmin2浏览0评论

In Aurelia application I have a "Rename" form with a single input

<input ... value.bind="something.name" />

and two buttons: Save and Cancel.

The same something object is already used in other controls. So, I don't want the name to change until Save button is clicked.

Is there a nice declarative way to acplish that or do I have to copy name to another property and then copy it back on Save trigger?

In Aurelia application I have a "Rename" form with a single input

<input ... value.bind="something.name" />

and two buttons: Save and Cancel.

The same something object is already used in other controls. So, I don't want the name to change until Save button is clicked.

Is there a nice declarative way to acplish that or do I have to copy name to another property and then copy it back on Save trigger?

Share Improve this question edited Oct 12, 2016 at 22:31 Jeremy Danyow 26.4k12 gold badges90 silver badges135 bronze badges asked May 11, 2016 at 19:32 Mikhail ShilkovMikhail Shilkov 35.2k4 gold badges77 silver badges110 bronze badges 5
  • AFIK there is no support for your demand out of the box. But if it is a repetitive demand, it would not be difficult to create a custom resource for it though. – qtuan Commented May 12, 2016 at 9:41
  • @qtuan Could you give me some guidance on how I could do it myself? – Mikhail Shilkov Commented May 12, 2016 at 9:46
  • With a custom resource, I mean to avoid the repetitive logic. What are your use cases? If it is always a form with a single input, an easy solution is to encapsulate the form in a custom element. – qtuan Commented May 12, 2016 at 10:00
  • @qtuan No, the forms are different (two so far). Use case is: we have a list, we add a popup to edit one item of the list, which is bound to save item that the list has. The list should not change until user presses Save. – Mikhail Shilkov Commented May 12, 2016 at 10:46
  • In fact I also have a similar demand and just tried a solution using custom binding behavior. I'll post my answer. – qtuan Commented May 13, 2016 at 3:39
Add a ment  | 

4 Answers 4

Reset to default 2

You can create a model object for your edit popup and only copy the edits made to the item in the list when Save is clicked. Here's a simplified example: https://gist.run/?id=af3af031c5acc4c46407679f5ab1376b

View

<template>
  <ul>
    <li repeat.for="person of people">${person.firstName} ${person.lastName} <button click.delegate="editPerson(person)">Edit</button></li>
  </ul>
  <div if.bind="editing">
    First Name: <input type="name" value.bind="editModel.firstName" />
    Last Name: <input type="name" value.bind="editModel.lastName" />
    <button click.delegate="savePerson()">Save</button>
    <button click.delegate="cancelEdit()">Cancel</button>
  </div>
</template>

ViewModel

export class App {
  editing = false;
  people = [
    { firstName: 'John', lastName: 'Doe' },
    { firstName: 'Jane', lastName: 'Smith' },
    { firstName: 'Bob', lastName: 'Smith' }
  ];

    editPerson(person) {
      this.editing = true;
      this.editObject = person;
      this.editModel = Object.assign({},person);
    }
    savePerson() {
      this.editing = false;

      Object.assign(this.editObject, this.editModel);

      this.editObject = null;
      this.editModel = null;
    }

    cancelEdit() {
      this.personBeingEdited = null;
      this.editing = false;
    }
}

I think Ashley Grant's answer is a very clear/straightforward way of doing this. I would go with something like that. Since you asked for an alternate approach, that uses less code, here you go.... not sure if it's any better, it's just another way of doing it, probably less clear...

https://gist.run?id=e931202307361d472c3e0ee4f523a833

The view model has a property called editPerson which represents the person that is currently being edited.

app.js

export class App {
  people = [
    { firstName: 'John', lastName: 'Doe' },
    { firstName: 'Jane', lastName: 'Smith' },
    { firstName: 'Bob', lastName: 'Smith' }
  ];

  editPerson = null;

  save() {
    this.editPerson.firstName = this.firstNameInput.value;
    this.editPerson.lastName = this.lastNameInput.value;
    this.editPerson = null;
  }
}

The view uses one-way bindings to push the view-model data into the inputs. Edits to the inputs will not update the model because the binding is one way. When the form is submitted the view-model's save() method will be called, which has logic to copy the input values into the model.

app.html

<template>
  <ul>
    <li repeat.for="person of people">
      ${person.firstName} ${person.lastName}
      <button click.delegate="editPerson = person">Edit</button>
    </li>
  </ul>

  <form if.bind="editPerson" submit.delegate="save()">
    <label>
      First Name:
      <input ref="firstNameInput" value.one-way="editPerson.firstName">
    </label>
    <label>
      Last Name:
      <input ref="lastNameInput" value.one-way="editPerson.lastName">
    </label>

    <button type="submit">Save</button>
    <button type="button" click.delegate="editPerson = null">Cancel</button>
  </form>
</template>

Here is a alternative using a custom "cancellable" binding behavior. The binding would do these things:

  1. Intercept updateSource to redirect writing to a hidden storage
  2. Listen for save event to write back the hidden storage to original source.

"2" is done by convention that observe a saved property on the binding context. If need more flexibility, the property name can be passed as an argument to the custom binding.

app.html

<template>
  <require from='./cancellable'></require>

  <div>
    Name: ${name}
    Age: ${age}
    <button click.delegate="edit()">Edit</button>
  </div>

  <div if.bind="editing">
    <h3>Cancellable edit</h3>
    Name: <input value.bind="name & cancellable">
    Age: <input value.bind="age & cancellable">
    <div><button click.delegate="save()">Save</button>
    <button click.delegate="cancel()">Cancel</button></div>
  </div>

  <div if.bind="editing">
    <h3>Instant edit</h3>
    Name: <input value.bind="name">
    Age: <input value.bind="age">
  </div>
</template>

app.js

export class App {
  constructor() {
    this.name = 'John';
    this.age = 20;

    this.editing = false;
    this.saved = false;
  }


  edit() {
    this.saved = false;
    this.editing = true;
  }

  save() {
    this.saved = true;
    this.editing = false;
  }

  cancel() {
    this.saved = false;
    this.editing = false;
  }
}

cancellable.js

import {inject} from 'aurelia-dependency-injection';
import {BindingEngine} from 'aurelia-binding';

@inject(BindingEngine)
export class CancellableBindingBehavior {
  constructor(bindingEngine) {
    this.bindingEngine = bindingEngine;
  }

  bind(binding, scope) {
    let value;    
    let modified = false;

    let cancellable = {
      originalUpdateSource: binding.updateSource,
      originalUpdateTarget: binding.updateTarget,
    };

    // 1. Intercept "updateSource" to redirect write to a hidden value storage
    binding.updateSource = (val) => {
      value = val;
      modified = true;
    };

    // 2. Intercept updateTarget" so that can observe change from original source 
    binding.updateTarget = (val) => {
      value = val;
      modified = false;
      cancellable.originalUpdateTarget.call(binding, val);
    }

    // 3. Observe the "saved" event to copy back to original source 
    let bindingContext = scope.bindingContext;
    cancellable.subscription = this.bindingEngine.propertyObserver(bindingContext, 'saved')
      .subscribe((newValue, oldValue) => {
        if (newValue && modified) {
          cancellable.originalUpdateSource.call(binding, value);
        }
      });

    binding.cancellable = cancellable;
  }

  unbind(binding, scope) {
    binding.updateSource = binding.cancellable.originalUpdateSource;
    binding.updateTarget = binding.cancellable.originalUpdateTarget;
    binding.cancellable.subscription.dispose();
    binding.cancellable = null;
  }
}

Gist run: https://gist.run/?id=2c7e40e88d1d3c18e9d2bca6be438b47

Reference: the built-in throttle binding behavior

What I've done is made a copy of the object I want to edit and then bind that to the view, when I click save I then 'merge' the editedObject with my originalObject so the changes are reflected in the other controls.

So my edit ViewModel looks like this:

activate(existingUser: User) {
    let userCopy: User = JSON.parse(JSON.stringify(existingUser));
}

In my view I then user the userCopy object to bind properties. When I click save I do the following:

let indexOfUser = this.users.indexOf(existingUser);
Object.assign(this.users[indexOfUser], userCopy);

I've simplified the code a bit because in my use case I'm working with a dialog that returns the editedUser to a different ViewModel and that's the ViewModel that holds the list of users etc.

发布评论

评论列表(0)

  1. 暂无评论