Listen when map property is set

Hi,

I have an asyncrhonous defined property like this:

quickbrief: {
	get(last,set) {
		return Quickbrief.get({quickbrief_number: this.attr('slug')}).then(set);
	}
}

The get() does an ajax request and when its resolved, the returned data is set to quickbrief.
The returned data has a complex structure composed by quickbrief attributes and nested submodel list/instances:

{
  "quickbrief_number": 1,
  "customer_id": 1,
  "customer_brand_id": 3,
  "customer_product_id": 2,
  "date_completion": "2017-03-02",
  "benefit": "This is the content text for benefit",
  "target": "This is the content text for target",
  "impact": "This is the content text for impact",
  "parent_quickbrief_number": null,
  "status": "PRESUPUESTADO",
  "is_open": false,
  "email": "gregorgodoy@gmail.com",
  "currency_code": "PYG",
  "created_at": "2017-01-05T17:09:04.370898-03:00",
  "quickbrief_detail": [
    {
      "quickbrief_number": 1,
      "product_id": 1,
      "parent_product_id": null,
      "prize": 25000000,
      "reference_url": "http:\/\/www.voleysur.org\nhttp:\/\/www.fivb.org\nhttp:\/\/www.fpv.org.py"
    },
    {
      "quickbrief_number": 1,
      "product_id": 9,
      "parent_product_id": 1,
      "prize": 3000000,
      "reference_url": "http:\/\/www.youtube.com\nhttp:\/\/www.vimeo.com"
    },
    {
      "quickbrief_number": 1,
      "product_id": 14,
      "parent_product_id": 1,
      "prize": 22000000,
      "reference_url": "http:\/\/www.audacityteam.org\nhttp:\/\/www.tunein.com"
    }
  ]
}

The returned data is a typical master/detail data structure.

ISSUE 1
When i do this.attr('quickbrief').save() the whole data is sent to the server, including the deatils (in this case quickbrief_detail).

Is there a way to prevent this? I just want to sent to the server attributes that correspond to quickbrief. Whats the best way / practice to do this?

ISSUE 2
In the stache file i have defined two-way live bindings for some properties of quickbrief:

<li>
	<div class="selector-wrapper">
		<select {($value)}="quickbrief.customer_id">
			{{#customers.value}}
			<option value="{{customer_id}}">{{trademark}}</option> 
			{{/customers}}
		</select>
	</div>
</li>
<li>
	<div class="selector-wrapper">
		<select {($value)}="quickbrief.customer_brand_id">
			{{#customerBrands.value}}
			<option value="{{customer_brand_id}}">{{name}}</option>
			{{/customerBrands}}
		</select>
	</div>
</li>

Please note quickbrief.customer_id and quickbrief.customer_brand_id.
The problem is that when quickbrief.customer_id or quickbrief.customer_brand_id is set i need an action to be fired, but the quickbrief set function is net fired:

quickbrief: {
	get(last,set) {
		return Quickbrief.get({quickbrief_number: this.attr('slug')}).then(set);
	},
	set() {
		console.log('THIS FUNCTION IS NEVER FIRED')
	}
}

Is there a way i can listen for when a specific property is set or changed? Actually im doing complex bindings to quickbrief before passing it to set in the get() function like this:

 quickbrief: {
	get(last,set) {
		return Quickbrief.get({quickbrief_number: this.attr('slug')}).then(function(qb){
			qb.bind('change', function(ev, attr, how, newVal, oldVal) {
				if (attr=='customer_id') {
					qb.attr('customer_brand_id', null);
				} else if (attr=='customer_brand_id') {
					qb.attr('customer_product_id', null);
				} else if (/quickbrief_detail\.[0-9]*\.product$/gi.test(attr)) {
					...				
				}
			});
			set(qb);
		});
	},
	set() {
		console.log('THIS FUNCTION IS NEVER FIRED')
	}
}

Is this a good practice? If not, what is suggested?

THANKS IN ADVANCE FOR ANY SUGGESTION!

Clone this JSBin to make examples.

Hi @Gregorio_Godoy,

For issue 1, I would look into serialize. It allows you to control which properties are serialized into JSON, as well as how they’re serialized.

For issue 2, the quickbrief setter is only fired when quickbrief is set on the view model, not when any of its properties change (as you’ve discovered).

I think a better pattern to follow would be setters for each of those specific properties. Here’s what your Quickbrief model could look like:

Quickbrief = can.Model.extend({
}, {
  define: {
    customer_brand_id: {
      set: function(newID) {
        this.attr('customer_product_id', null);
        return newID;
      }
    },
    customer_id: {
      set: function(newID) {
        this.attr('customer_brand_id', null);
        return newID;
      }
    }
});

Please let me know if that makes sense!

Can you do this with nested properties?

Yes, for a nested object, just create another Type with the nested properties: https://v2.canjs.com/docs/can.Map.prototype.define.TypeConstructor.html

If you’re using can-define in CanJS 3, this was made even easier; you can define Type with an object instead of making a constructor function: http://canjs.com/doc/can-define.types.TypeConstructor.html#propDefinition

1 Like

So this should work?

the Type is working correctly in your example, but the value can’t currently take a PropDefinition like you’re trying to do.

I think this is what you’re looking for:

Thanks for your suggestion, but it looks like set is never called on data.test when using can-stache-converters.

Also how can you tell which key is being updated?

data.test isn’t being set when you’re just changing one of it’s properties. You can add setters on the individual properties on the PropDefinition you’re creating on Type:

1 Like

Yup that is exactly it. Thanks!