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

Make getter / setter converter functions possible with stache helpers #2299

Closed
justinbmeyer opened this Issue Mar 2, 2016 · 9 comments

Comments

Projects
None yet
2 participants
@justinbmeyer
Contributor

justinbmeyer commented Mar 2, 2016

I'd like to make it possible to make simple two-way string <-> number converters as stache helpers available to two-way binding logic. For example:

<input {($value)}="numberToString(~age)">

This would convert an input value of:

  • "" to null.
  • "9" to 9

It would do the opposite too. An age of null or undefined would be converted to "".

For this to work, we mostly need to make it so when a CallExpression is set, it passes that new set value to the stache helper function.

The problem is that identifying that the helper was called with a "new value" can be tricky if arguments.length is the only indication.

So, I'm thinking of passing the new value as the first argument, but wrapping it in a can.expression.SetIdentifier type.

This will make the helper above's implementation look like:

can.stache.registerHelper("numberToString", function(newVal, source){
    if(newVal instanceof can.expression.SetIdentifier) {
        source(newVal.value === "" ? null : +newVal.value );
    } else {
        source = newVal;
        return source() + "";
    }
});

Gross, yes. But we can clean this up with other helper types if we need to:

can.stache.registerConverter("numberToString",{
  get: function(source){
    return source() + "";   
  },
  set: function(newVal, source){
    source(newVal === "" ? null : +newVal); 
  }
});

Thoughts?

cc @jandjorgensen

@justinbmeyer justinbmeyer added this to the 2.3.18 milestone Mar 2, 2016

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 2, 2016

Contributor

Although we might put this in 2.3.18, it will not be documented, so don't use it.

Contributor

justinbmeyer commented Mar 2, 2016

Although we might put this in 2.3.18, it will not be documented, so don't use it.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 2, 2016

Contributor

Another important one might be:

<input type="checkbox" {($checked)}=“inList( list, item )" />
can.stache.registerConverter("inList",{
  get: function(list, item){
    return list.indexOf(item) !== -1;   
  },
  set: function(newVal, list, item){
    var index = list.indexOf(item);
    if(newVal){ 
      if( index === -1) {
        list.push(item)
      }  
    } else {
      if( index !== -1 ){
        list.splice(index,1);
        // might need to look for others here
      }
    }
  }
});
Contributor

justinbmeyer commented Mar 2, 2016

Another important one might be:

<input type="checkbox" {($checked)}=“inList( list, item )" />
can.stache.registerConverter("inList",{
  get: function(list, item){
    return list.indexOf(item) !== -1;   
  },
  set: function(newVal, list, item){
    var index = list.indexOf(item);
    if(newVal){ 
      if( index === -1) {
        list.push(item)
      }  
    } else {
      if( index !== -1 ){
        list.splice(index,1);
        // might need to look for others here
      }
    }
  }
});
@dylanrtt

This comment has been minimized.

Show comment
Hide comment
@dylanrtt

dylanrtt Mar 2, 2016

Contributor

What if {($value)} bound to the result of the subexpression? You could simply return a compute in the helper like this:

can.stache.registerHelper('convertNumber', function(source) {
  return can.compute('', {
    get() {
      return source()+'';
    },
    set(val) {
      source(val === '' ? null : +val);
    }
  });
});

When I try to do this now, the compute function string is shown in the text box which doesn't seem useful.

Contributor

dylanrtt commented Mar 2, 2016

What if {($value)} bound to the result of the subexpression? You could simply return a compute in the helper like this:

can.stache.registerHelper('convertNumber', function(source) {
  return can.compute('', {
    get() {
      return source()+'';
    },
    set(val) {
      source(val === '' ? null : +val);
    }
  });
});

When I try to do this now, the compute function string is shown in the text box which doesn't seem useful.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 2, 2016

Contributor

@dylanrtt this was the first thing I tried (and got working through some ugly hacking). However, I worried what if someone did something like:

can.stache.registerHelper('convertNumber', function(source, radix) {
  var rad = radix();
  return can.compute('', {
    get() {
      return source()+'';
    },
    set(val) {
      source(val === '' ? null : parseInt(val, radix) );
    }
  });
});
Contributor

justinbmeyer commented Mar 2, 2016

@dylanrtt this was the first thing I tried (and got working through some ugly hacking). However, I worried what if someone did something like:

can.stache.registerHelper('convertNumber', function(source, radix) {
  var rad = radix();
  return can.compute('', {
    get() {
      return source()+'';
    },
    set(val) {
      source(val === '' ? null : parseInt(val, radix) );
    }
  });
});
@dylanrtt

This comment has been minimized.

Show comment
Hide comment
@dylanrtt

dylanrtt Mar 2, 2016

Contributor

@justinbmeyer If you don't intend on radix changing, that doesn't seem like an issue. However, if you do, what would be the intended functionality? Should it re-run the setter?

Contributor

dylanrtt commented Mar 2, 2016

@justinbmeyer If you don't intend on radix changing, that doesn't seem like an issue. However, if you do, what would be the intended functionality? Should it re-run the setter?

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 2, 2016

Contributor

My guess is that a new compute would be created and then value would be connected to that new compute.

The problem is there's not a good way of having computes within computes in this way. The outer compute creates and depends on the inner compute. So if any other dependency of the outer compute changes, a new inner compute is created.

Contributor

justinbmeyer commented Mar 2, 2016

My guess is that a new compute would be created and then value would be connected to that new compute.

The problem is there's not a good way of having computes within computes in this way. The outer compute creates and depends on the inner compute. So if any other dependency of the outer compute changes, a new inner compute is created.

@dylanrtt

This comment has been minimized.

Show comment
Hide comment
@dylanrtt

dylanrtt Mar 2, 2016

Contributor

In order for radix to be a compute, I assume it would also be passed as ~radix which seems like an unusual use case. If it was passed without ~, then when radix changes, I suppose the outer compute would recompute the inner, which should then be re-bound and re-set... Seems like complicated behavior for a binding.

Contributor

dylanrtt commented Mar 2, 2016

In order for radix to be a compute, I assume it would also be passed as ~radix which seems like an unusual use case. If it was passed without ~, then when radix changes, I suppose the outer compute would recompute the inner, which should then be re-bound and re-set... Seems like complicated behavior for a binding.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 3, 2016

Contributor

@dylanrtt yeah, this is why I think my solution is technically easiest. It just needs some API love ontop to make it easy for people to make their own two-way converters.

Contributor

justinbmeyer commented Mar 3, 2016

@dylanrtt yeah, this is why I think my solution is technically easiest. It just needs some API love ontop to make it easy for people to make their own two-way converters.

@daffl daffl closed this in #2300 Mar 3, 2016

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 4, 2016

Contributor
var data = {
  people: new can.List([{name: "alexis"}, {name: "brian"}]),
  state: new can.Map();
}
<select {($value)}="selected(~data.state.person, data.people)">
  {{#each data.people}}
    <option value="{{%index}}">{{name}}</option>
  {{/each}}
</select>
can.stache.registerHelper("selected", function(newVal, item, list){
    if(newVal instanceof can.expression.SetIdentifier) {
        item( list.attr(newVal) );
    } else {
        list = item;
        item = newVal;
        return list.indexOf( item() );
    }
});
Contributor

justinbmeyer commented Mar 4, 2016

var data = {
  people: new can.List([{name: "alexis"}, {name: "brian"}]),
  state: new can.Map();
}
<select {($value)}="selected(~data.state.person, data.people)">
  {{#each data.people}}
    <option value="{{%index}}">{{name}}</option>
  {{/each}}
</select>
can.stache.registerHelper("selected", function(newVal, item, list){
    if(newVal instanceof can.expression.SetIdentifier) {
        item( list.attr(newVal) );
    } else {
        list = item;
        item = newVal;
        return list.indexOf( item() );
    }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment