I'd like a custom control that uses ngModel.$formatters to be able to format data as soon as a server dependency loads in. In my case, it needs to load a lookup table to go from one kind of id to another. $modelValue stores one thing $viewValue displays another. Pretty straight-forward stuff.
The trick is that if my lookup table isn't loaded, I can't do the formatting into a $viewValue.
Once my data loads, I need to do the following:
ngModel.$formatters.push(myFormatter)
Tell ngModel to start the pipeline from $modelValue -> $formatters -> $viewValue
$render() doesn't work, this just moves the value from $viewValue into the UI control.
$rollbackViewValue() looks promising, but that's only in an unstable version (1.3.0-beta.18).
Code Sample:
mappingTable.load().then(function(data){
mappingData = data;
ngModel.$formatters.push(myFormatter); // needs mappingData in order to function
// TODO: Tell ngModel to run the existing $modelValue through $formatters to calculate a new $viewValue and $render it
//ngModel.$render() // doesn't work, only puts the $viewValue in the DOM element.
});
Looking at the code for ngModelController, it appears that what you stumbled upon (setting $modelValue to anything other than the current actual model value) is the accepted way to do this. As you say, the value you set is not used: it just triggers the update. Check its current value first to make sure it actually changes (or use a very unlikely value).
if (ngModel.$modelValue == 'bar')
ngModel.$modelValue = 'foo';
else
ngModel.$modelValue = 'bar';
Here is a related question.
Also, there is an active pull request that looks like an "official" way of doing this is forthcoming.
The reason it works is that ngModelController sets up a $watch that runs every digest cycle that compares $modelValue to the value that ng-model is bound to. If they don't match, it triggers the $formatters pipeline.
Related
I'm coding a small Vue app. I've got an element which has a data-range property written like this:
:data-range="form.appearence.height_min + '/7'"
form.appearence.height_min will change based on a select element values, selected by the user.
After every select change, I'll read again the data-range and do things based on it.
// from the vue app, a watcher
'form.appearence.xps':function(val, oldval){
// this will properly change the model and the dom as well
this.$set(this.form.appearence, 'height_min', xps_map[val]);
this.$emit('xps-updated');
}
// then from another script
this.options.vue.$on('xps-updated', function(){
this.options.vue.$nextTick(function(){
console.log($('#test5').data('range')) // issue: this value doesn't change
}.bind(this))
}.bind(this));
My issue is that the range value does change on dom, I can see it from console, but javascript will always read the initial value... For example, at start was 3/7, then it gets changed to 5/7, but $('#test5').data('range') will still read 3/7. Why?
Ok, I'll answer by myself. I found out that jquery objects do not follow Vue's dom updates, at least in this case. Therefore, even if dom gets updated by Vue, $('#test5').data('range') will always give the initial value.
Instead, by getting the 'real' element with vanilla js, like
let range = document.querySelector('#test5').dataset.range;
Will always return the updated value.
In knockout I want to write an ComputedObservable which is computed from values which are not observable. I want to manually trigger the Notification.
Is this possible somehow?
A computed variable is nothing but a function that registers custom event on all observables inside it.
When ever your internal observable variable changes, it broadcasts a notify event and all listeners catches the event and process accordingly.
The process sounds simple but isn't when you plan to use on plain Javascript variable. You can refer Listening for variable changes in JavaScript or jQuery.
Now if you wish to achieve this manually, basic challenges:
A variable can be changed anywhere, anytime. You will have to manually trigger their notify event everywhere. If you miss, you will have incorrect data.
You will also have to add eventListeners for bindings. Like a total variable which should be computed.
Browser support. IE8 or before does not support custom events and you will have to add hacks for it.
My suggestion, use computed (or pureComputed if on or above KO3.2) with observables. This way you will save a lot of code. Knockout team must have gone through these issues and have added handling for it in their code. You reinventing the wheel will add a lot of code in your codeBase and without proper documentation, will be difficult to maintain.
Following is a Fiddle where I replicated textInput binding for number input. If you see, they have separate handling of IE10, IE9 and IE8 or below. They even have special handling for safari below 5.
I fully agree with the other answer on just using ko.observable variables in your computed. I don't see why you'd use a computed if its re-evaluation isn't automated. I'd advice to create a plain function instead.
The closest approach I'd see is to create a dummy observable that the computed subscribes to. You can call valueHasMutated to force re-evaluation:
var a = "A";
var b = "B";
var subscriptionObs = ko.observable();
var ab = ko.pureComputed(function() {
subscriptionObs();
return a + b;
});
ko.applyBindings({ab: ab });
b = "C";
subscriptionObs.valueHasMutated();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p data-bind="text: ab"></p>
I'm trying to create a simple function which, when ribbon button is pressed, sets entity attribute value to null.
Now the problem I am facing is, that the changes I make to the entity are not saved, form reloads and returns previous value.
To the button event I pass 'Task' activity attribute 'actualend'. 'Actual End' field is disabled by default.
ClearField: function (field) {
if (Xrm.Page.getAttribute(field) == null) return;
Xrm.Page.ui.controls.get(field).setDisabled(false);
Xrm.Page.getAttribute(field).setSubmitMode("always");
Xrm.Page.getAttribute(field).setValue(null);
if (Xrm.Page.data.entity.getIsDirty()) {
Xrm.Page.data.entity.save(); //also tried addOnSave(function)
}
}
Following debugger I was able to track that all changes are made correctly, except that on save() method they are 'discarded', then form reloads with previous value. This code works fine with CRM UR8 yet with CRM UR13 it does not.
Am I missing something?
As Guido mentions in his comment the code looks good which leads me to think that one of your two if statements is failing.
The first one obviously will fail if it is set to null. A little bit less obvious is that it will fail as well as if the field is not actually on the form (even though it may be a valid attribute of the entity). So step 1, ensure that your field exists on the form.
The second one I'm not sure of... I don't think getIsDirty() is keeps track of programmatic changes, so even though your programmatically updating a field and setting it to always submit, it may be returning false. Regardless of how exactly it's working, the if statement really isn't needed. The Xrm.Page.data.entity.save function will only actually save if it has some value that has changed, so I'd remove your dirty check regardless.
Eh, the problem with my issue all this time was that even though the field existed in the form, it never had an entity passed to it, therefore it was unable to save it. I've edited a ribbon button so to pass entity through CrmParameters and the issue was gone. Thank you both for supplying me with possible solutions regardless!
I want to detect a change in a new record. However, the record is new and dirty from the moment I create it.
var record = transaction.createRecord(App.ContentOfSomeSort, data);
record.get('isNew'); // true
record.get('isDirty'); // true
Is there an event or property that I can observe/listen for that tells me when the record has changed? I would think that isDirty would be good for this, but it's dirty as soon as I create it.
It looks like didSetProperty might be what you are looking for
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/attributes.js#L65
Object.keys(model.changedAttributes()).length === 0 will work if you're just checking for plain attributes. changedAttributes() has an open issue right now where it will not report if relationships on the model have been changed.
I don't know ember.js (maybe there's some native function/event in it?), but can't it be done by setting interval which compares previous record value with actual one?
I am trying to bind a property of an object to a property that's bound in an ArrayController. I want all of this to occur after the object has already been created and added to the ArrayController.
Here is a fiddle with a simplified example of what I'm trying to achieve.
I am wondering if I'm having problems with scope - I've already tried to bind to the global path (i.e. 'App.objectTwoController.objectOne.param3') to set the binding to. I've also tried to bind directly to the objectOneController (which is not what I want to do, but tried it just to see if it worked) and that still didn't work.
Any ideas on what I'm doing incorrectly? Thanks in advance for taking the time to look at this post.
So in the example below (I simplified it a little bit, but same principles apply)... The method below ends up looking for "objectOne" on "objectTwo" instead of on the "objectTwoController".
var objectTwoController: Em.Object.create({
objectOneBinding: 'App.objectOne',
objectTwoBinding: 'App.objectTwo',
_onSomething: function() {
var objectTwo = this.get('objectTwo');
objectTwo.bind('param2', Em.Binding.from('objectOne.param3'));
}.observes('something')
});
The problem is that you can't bind between two none relative objects. If you look in the "connect" method in ember you will see that it only takes one reference object (this) in which to observe both paths (this is true for 9.8.1 from your example and the ember-pre-1.0 release).
You have few options (that I can think of at least).
First: You can tell the objects about each other and in turn the relative paths will start working. This will actually give "objectTwo" an object to reference when binding paths.
....
objectTwo.set('objectOne', this.get('objectOne');
....
Second: You could add your own observer/computed property that will just keep the two in sync (but it is a little more verbose). You might be able to pull off something really slick but it maybe difficult. Even go so far as writing your own binding (like Transforms) to allow you to bind two non-related objects as long as you have paths to both.
_param3: function(){
this.setPath('objectTwo.param2', this.getPath('objectOne.param3');
}.observes('objectOne.param3')
You can make these dynamically and not need to pre-define them...
Third: Simply make them global paths; "App.objectOneController.content.param3" should work as your binding "_from" path (but not sure how much this helps you in your real application, because with larger applications I personally don't like everything global).
EDIT: When setting the full paths. Make sure you wait until end of the current cycle before fetching the value because bindings don't always update until everything is flushed. Meaning, your alert message needs to be wrapped in Ember.run.next or you will not see the change.