This isn't an ideal situation, but due to another knockout binding I am using I am in a situation where I am needing to get the element an observable is bound to, if it is indeed bound to anything.
So is there a way to do this?
== Update ==
I didn't want to add any extra context incase it confuses the question, but as it may get an answer more in line with expectations here is the scenario.
I am using the knockout validation binding, which exposes all the errors using the ko.validation.group(model) method. However the problem is that only gives you the textual errors, it does not give you any context as to what part of the model gave you those errors. So I have made a small change to the source to now pass back the observable tied to each error, as this may be useful for a few scenarios, but from here I need to be able to tie this to an element so I can display some in-line validation of some kind.
Knockout Validation provides a very basic in-line validation where it creates a span after your element and you can give it a class, but this is too basic for my needs as currently we are using Qtip and other notification systems to display validation errors, and because of this I need to be able to have a Dom element and an error. So far I have an observable and an error, but I need to tie that observable object (which could be any ko.observable() property from the model) to its given DOM element, if it does have an element binding.
As all I have is an object and I am using validation driven from the model not the UI, the problem is not really going to be solved via a custom binding. Ideally I need to be able to crack open the marry up the observable object (an unknown ko.observable()) to an element.
Not to go too project specific, but my current project abstracts validation where events are fired off (i.e EventSystem.SendEvent(ValidationEvents.ValidationFailed, <element>, <error>)) then a validation system listens for these events and ties the error to the element, be it a tooltip, a growl style notification, an alert box etc. So I am trying to find the best way to keep this abstraction when driving the validation from the models observables not the ui's DOM elements (i.e jquery-ui)
== Edit 2 ==
I was a bit thrown by the way Knockout Validation knows the elements for observables to put in its own validation elements, however they just piggy back off the existing value binding, so I am just going to change that to add the elements for any validation elements based on their isValidatable() method, at least that way for each error I can tie it to an observable, and for any observables with element bindings I can tie them to the elements, and if there are none then it is fine they would just be form wide validation errors. I will give this a try as this should be something like (not tested yet):
if(utils.isValidatable(valueAccessor())) {
valueAccessor().extend({hasElementalBinding: true, elementalBinding: element});
}
else {
valueAccessor().extend({hasElementalBinding: false});
}
At around line 250 in the registerValueBindingHandler, I will leave this question open for a while longer incase someone else has a better solution.
I have done something similar to what you mentioned above. My data-bind tag includes a custom binding:
data-bind="... myvalidationbinding: myobservable"
Then in my binding handler I extend the observable
ko.bindingHandlers.myvalidationbinding = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
valueAccessor().extend({element: element });
}
};
And finally my extension is
ko.extenders.element = function (target, element) {
target.DOMElement = element;
}
Now I can subscribe to isValid() given by knockout.validation and if not valid, go get the element the observable is bound to and then manipulate it with jQuery.
This won't be very fast, so I would definitely cache the results, but something using jQuery's attribute selectors:
$('[data-bind*="Property"]')
*= is the attribute contains selector: http://api.jquery.com/attribute-contains-selector/
Obviously this won't catch anything that subscribed manually using the .subscribe method, but I'm not sure how you would extract element's from the functions anyway.
Disclaimer: while this solution will probably work, this sounds like a horrible idea to me, I would instead write a custom binding (as mentioned in the comments) or find some other solution.
Related
I use template driven and am trying to initialize my form using patchValue and it is not working.
I can make it work if I use two-way binding to set values [(fooBar)] or even setTimeout but I was just wondering, is there a way to make it work with just patchValue?
https://stackblitz.com/edit/angular-fft2c5
Thank you.
It doesn't work because at the time of calling patchValue method there are no any controls registered in your form yet.
Why?
That's because Template-driven forms are asynchronous. They delegate creation of their form controls to directives. To avoid "changed after checked" errors, these directives take more than one cycle to build the entire control tree. That means you must wait a tick before manipulating any of the controls from within the component class.
Moreover, if you try using setValue method instead of patchValue Angular will even warn you how to deal with it.
this.myForm.control.setValue({name: this.name});
ERROR Error: There are no form controls registered with this group
yet. If you're using ngModel, you may want to check next tick (e.g.
use setTimeout).
So, as you already discovered, you have to either use [ngModel] binding or wait next tick by using e.g setTimeout or requestAnimationFrame
Scheduling microtask should also work if you would use it in ngAfterViewInit hook:
ngAfterViewInit() {
Promise.resolve().then(() => {
this.myForm.control.patchValue({ name: this.name });
});
}
In template driven forms, you need to bind [(ngModel] to set values.
Try like this:
.html
<input name="name" [(ngModel)]="formValue.name"/>
.ts
formValue:any = {}
ngOnInit() {
this.formValue = {name: this.name}
}
Working Demo
I would like to have a polymer element with two sub-elements, one that produces data, and the other that performs some action when the data changes (in my case: sending a notification to a server).
To implement this, I wrote a polymer element, namely root, with the following structure (names changed to simplify the discussion):
<producer data={{foo.bar}}></producer>
<consumer data=[[foo]]></consumer>
The producer changes the data using the set('property', 'value') method, so that the root element sees the notifications. The problem is that the consumer element won't notice the changes to foo since they involve a sub-property.
To solve this, I tried using a computed binding as follows:
<producer data={{foo.bar}}></producer>
<consumer data=[[_compute(foo)]]></consumer>
...
_compute: function() {
return this.foo;
}
However this won't cause the consumer to be notified. I think the reason for this is that the returned object is the same reference (only a sub-attribute changed). Currently the workaround I've used is to use the following version of the compute function:
_compute: function() {
return Object.assign({}, this.foo);
}
This works (the consumer element gets notified), however I'm affraid it might not be the most efficient (I'm creating an object at every call of _compute) and/or elegant way. Then my question is: what is the proper way to achieve this behavior in Polymer?
Do you have access to modify the consumer element?
The best way to fix this is to have the consumer element have a multi-property observer that listens for sub-property changes on the data property.
It might look something like this:
Polymer({
is: 'consumer',
properties: {
data: Object
},
observers: ['consumeData(data, data.*)'],
consumeData: function (data) {
//Do whatever you were planning on doing with data here
}
});
The advantage of an approach like this is that your 'consumer' element just 'knows' how to consume the data object when a sub-property on it changes. Because of the lighter weight approach to data binding in Polymer, trying to implement this behavior outside of the 'consumer' element will necessarily be more expensive and more complicated, since it requires either tricking the data binding into thinking the data object is new by supplying it with a new reference to a copy or forgoing the data binding altogether and building an approach on top of events and calling methods on the consumer in response to events. So if at all possible, I would recommend trying the approach above.
Polymer's data binding does not work the same way as some other two-way enabled data binding implementations, like what you might find in AngularJS. Rather than using dirty-checking, which is extremely expensive, Polymer uses an event based 'path notification' approach. When a sub-property on a property changes, a Polymer element which has that property will fire an event to it's immediate children bound to that property, notifying them that the path 'property.subProperty' has changed. In order for consumer to act on those changes, it has to be told to listen to changes along that 'property.subProperty' path. We specify paths in our polymer observers by using the syntax above. In this case, putting data.* in our observer means we want to listen to any path off of data, so that any notified property change on the data property will trigger the observer.
As you have noticed there isn't an elegant way of doing this. The way you got it working is interesting.
An alternative way which I would expect to work would be to fire an event from within the producer element.
this.fire('data', {data: this.foo.bar});
and then have the parent/root element listen for this event and then update the data property of the consumer element.
<producer on-data="handleData"></producer>
<consumer id="consumer"></consumer>
handleData: function(e) {
self.$.consumer.data = e.detail.data;
}
Edit:
You could make a new property that you compute within the producer element. Then you won't have to do a computed function everytime you want to access foo.bar
Producer element
properties: {
foo: {},
bar: {
computed: 'computeBar(foo)'
}
}
Root element:
<produce bar="{{bar}}"></producer>
<consumer data="[[bar]]"></consumer>
For most JS frameworks and libraries, the value they bring are often in the form of a new structure as to how to build an application (Backbone, React), or new ideas that effectively power-up the language (Angular), or simply the methods they offer are well tested, fast, and really convenient (jQuery).
Usually the ideas and methods they provide are pretty straightforward usage of JavaScript, but with a very clever team behind it that find interesting ways to do things which you can think through and get a solid guess as to how the guts work.
However, I've been unable to think through the ability to two-way bind JS models to view components. What is the secret sauce at the heart of this feature that makes this work? Changing an internal variable from a user input is simple, but what about the reverse? How would you be able to "know" when a JS variable has changed in order to update the display instantly? Surely it can't be polling, so what then?
Whenever a block of your JS runs that angular triggered it will run a digest cycle when the block finishes executing. This basically checks all the values that might of changed and would require updates to the view.
If angular didn't trigger the code then it won't know that something might of changed so your bindings can get out of sync. For example if you run something like this
setTimeout(function() {$scope.myValue = '123'});
Angular won't know that myValue changed and it actually won't update the view. That's why Angular has it's own services for doing everything. e.g. $timeout or $http.
If you have some callback function that Angular doesn't know about then you can manually tell it to check for changes by calling $scope.$apply()
there are several ways to do it. Object.observe is great, but lacks good support. You can poll for values as well, keeping a 2nd copy of the object around to compare. You can also write your own explicit set/get methods to update the model like backbone does.
One neat method i use a lot is using getters/setters to keep the model synced to the dom:
//a demo "model" of data:
model = {
name: "Fred"
};
function change(k,v){alert([k,v]);} // a stand-in change monitor for demo
// iterate model and replace values with getter/setter combos:
Object.keys(model).forEach(function(key) {
var val = model[key];
delete model[key];
Object.defineProperty(model, key, {
get: function() {
return val;
},
set: function(v) {
val = v;
change(key, val);
} //call change upon setting
});
change(key, val); //update view "onload"
}); // alerts "Fred";
//update model (fires change() with "name" and "sally" arguments:
model.name="sally"; // alerts "sally";
the change function is quite simple and for your case should just find elements bound to keys.
the advantage here is that you don't need special custom CRUD methods, you can just modify the object properties via assignment like it's 1999. It also doesn't poll, and works correctly all the way back to IE9 and any other ES5 environments. It's the simplest way to bind JS>DOM (afaik) without custom methods.
It does have some limits: nested objects are tricky to get/set upon, you can't do the whole object at once, you can only "watch" primitives. Arrays are a problem too: you can't really replace expando properties with getters/setters without side-effects. But, upon a relatively flat collection of JSON-safe data, get/set works a charm and needs no complex libs to get operational.
checkout a complete example using this method: http://pagedemos.com/xg3szbguqnwu/4
I can speak to how it's done in Backbone, which has a relatively low-level perspective on data-binding.
It's a combination of 1. the library having control over attribute setter methods 2. invoking callback functions when attributes change (e.g. by dispatching events) in order to update the UI.
The essential pseudocode is this:
class Model:
method set(name, value):
if value != this.attributes[name]
this.triggerEvent('change', name, value)
this.attributes[name] = value
m = new Model()
someInputWidget.onEvent('userChangedInput', function(value) {
m.set(someInputWidget.name, value)
})
m.onEvent('change', function(name, value) {
getInputWidgetByName(name).setValue(value)
})
Backbone does not do any data binding to the UI, but you can refer to Backbone's annotated source for the actual event-dispatching implementation.
I've a view with knockout.js which has some textboxes and dropdowns.
known when the user changes a value i save the data with a $post
for this i created some computed propties like
self.subjectChanged ko.computed(function () {
var subject self.subject();
//save...
But this also triggers when the subject was loaded from database and set for first time.
What is the best practice for this ?
A similar problem is that i have a function getdata() which depends on two properties.
Now on load this method is raised twice (for each property)
What are best practices to handle this szenarios ?
One way of doing it is to load the page and bind the data as normal, and then use subscriptions to monitor changes to the observable you are interested in.
http://knockoutjs.com/documentation/observables.html#explicitly-subscribing-to-observables
viewModel.subject.subscribe(function(newValue) {
// code you want to run when the value changes...
});
for example http://jsfiddle.net/m8mb5/
This may not be best practice, but in the past I tied a loaded variable to the vm and when the data finished loading from the server I set it to true;
In my computeds I would surround the code that actually did the work in an if that checked the loaded. Computeds can be a little tricky though, you may need to reference the observables outside of the if to ensure they fire correctly.
com = ko.computed(function(){
if(loaded){
var subject = self.subject();
}
// reference observable outside of if to ensure the computed fires when the observable changes
self.subject();
});
I've created an ember component that wraps an editor (CKEditor). The editor's values are updated via setData() and getData() accessors. I want to implement two-directional binding in my ember control so that edits to the component's "content" field flow in and out of the control.
So far, I'm able to get it going one way easily - but my attempts to go bidirectional are very messy. I can set up an observer on the property and have it update the control. However, when I try to set the property when the controller's "change" event is called, it causes the observer to be triggered. That, in turn causes the editors "change" event to trigger and so on. Welcome to Loopy Land.
I know that there are ways to get around this - but everything that I've been trying has me coming up short. It seems hacky - not elegant like the rest of Ember. Can anyone suggest some examples that demonstrates the preferred pattern for this?
Thanks!
--
(Thanks David - Here is some Additional Information)
I've been trying the bound property thing. It works great for outbound updates (from the editor control to another bound textarea on the page) but when inbound the page starts to bog down.
When I initialize the CKEditor, I reference a component that I installed that adds a 'change' event:
editor.on('change', this.updateContent.bind(this));
Here is the update content event:
updateContent: function() {
this.set('_content', this.get('editor').getData());
},
And then, the bound property:
content: function(key, val, previous)
{
if (arguments.length > 1)
{
this.set('_content', val);
var editor = this.get('editor');
if (editor) editor.setData(val);
}
return this.get('_content');
}.property('_content'),
It sounds like you are attempting to update a computed property from your control. If you have a computed property of fullName which depends on firstName and lastName, then it gets confusing if your UI updates the dependencies and not the computed property.
But if you really need to update the computed result, then look at the "Setting Computed Properties" section in the Ember docs (http://emberjs.com/guides/object-model/computed-properties/) and it shows you how you can use the input to the computed property to update its dependencies.
Not sure if this addresses your requirement, but if not pls submit a snippet of what's looping and what needs to be updated.