Knockout - Computed Observable with manual Change notification - javascript

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>

Related

Rx.js fromEvent + flatMapLatest broken?

Well, the problem itself is kind of hard to describe briefly, so here's a live example to demonstrate. It seems like I'm misunderstanding something about how Rx.js works, otherwise the functionality here comes from a bug.
What I tried to do was a simple reactive rendering setup, where what you see on the screen, and what events happen are both described in terms of Observables. The problem is that, for some indiscernible reason, the events are dropped entirely when the code is written one way, yet work fine with code that should theoretically be equivalent.
So, let's start with the first case in the example code above:
var dom = makeBox('one');
var clicks = Rx.Observable.fromEvent(dom, 'click');
If you create a DOM fragment, then you can simply use fromEvent to get an Observable for whatever event it emits. So far, so good. You can click this box and see a bunch of lines written to the log.
Now, the next step would be to make the DOM reactive, to express how it changes over time.
var domStream = Rx.Observable.return(makeBox('two'));
var clicks = domStream.flatMapLatest(function(dom) {
return Rx.Observable.fromEvent(dom, 'click');
});
That would make it an Observable, using return here to produce the simplest, constant case. The events you're interested in would be the ones emitted by the latest version of the dom, and that's exactly what the flatMapLatest operator does. This variant still works.
Ultimately, the goal would be to generate the current DOM state based on some application state. That is, map it from one Observable to another. Let's go with the simplest version for now, have a single constant value as the state, and then map it to the same fixed output we used previously:
var updates = Rx.Observable.return(1);
var domStream = updates.map(function (update) {
return makeBox('three');
});
var clicks = domStream.flatMapLatest(function(dom) {
return Rx.Observable.fromEvent(dom, 'click');
});
This should not be any different from the previous version. However, this outputs no events, no matter what you do.
What exactly is going on here? Did I misunderstand some fundamental concept of Rx, or what? I've run into some issues with hot vs cold Observables, but that seems unrelated in this minimal case. So, I'm kind of out of ideas. Can anyone enlighten me?
Sorry to tell you but it is a Hot vs Cold issue.
It is a subtle issue, but the difference between
Rx.Observable.return(makeBox('two'))
and
Rx.Observable.return(1).map(function() {return makeBox('three'); })
Is that the first returns a constant every time you subscribe to it, that is,
a box that you created initially. The second returns a new box every time the Observable is subscribed to, this causes a problem since you actually subscribe to the domStream variable twice, you are creating two instances of Box three, one which has event handlers but isn't shown and one that does not and is shown.
The fix is that you either need to use approach 2 or you need to convert the third into a hot stream either by using:
domStream.replay(1).refCount()
Or by using
domStream.publish()
then after all subscriptions are completed:
domStream.connect()

jQuery ".triggerHandler()" vs. ".trigger()" when multiple elements are selected

The jQuery ".triggerHandler()" mechanism, unlike ".trigger()", only operates on the first element referenced by the jQuery object for which it's called. In other words,
$('.all-over-the-page').triggerHandler("readjust");
will only call the "readjust" handler for the first element with class "all-over-the-page", even if there are many elements on the page with that class. The ".trigger()" method, on the other hand, would affect all of them.
I realize that I can use ".each()" to get around this (or simply write my own substitute that does that for me), but is there some rationale for why the two are different in this respect? It kind-of makes no sense to me. (I understand of course that it almost certainly can't be changed now.)
edit to clarify:
It's probably easier to understand why I'm scratching my head over this if I provide a context in the style of code I've actually got. When I put together code for various "widget" features on a page, that often involves event handlers. A good example is a form of some sort that's got some fields whose relevance is controlled by a checkbox, or radio button, or selector. A common instance of that is the "Shipping Address" checkbox that shows up on a zillion e-commerce sites: if the checkbox is checked, the shipping address is disabled and the billing address is used.
Now consider that some other code may, for its own reasons that are totally independent of the checkbox-control widget, actually do things to the form that may include updating checkbox settings programmatically. In that case, that other widget code may want to use "triggerHandler()" to tell any widgets, "hey I've updated some stuff, so you might want to re-check the current status and adjust if necessary."
Thus, if ".triggerHandler()" would operate on all selected elements, I could use:
$theForm.find('input, select, textarea').triggerHandler('change');
and all those handlers could run and do whatever they need. As I said, it's easy enough to write:
$theForm.find('input, select, textarea').each(function() {
$(this).triggerHandler('change');
});
"...is there some rationale for why the two are different in this respect?"
I think the idea is that triggerHandler() is meant to be a way of invoking the function you as a handler as though it was any other function.
As such, they made triggerHandler() so that the function is only invoked once, it returns the actual return value of the function, and it doesn't affect the DOM with bubbling or default behaviors.
Of course the function may break if they changed the this value to something other than a DOM element, so they just use the first element matched.
If you're wanting to simply use your function, then I'd probably just keep a reference to it and invoke it directly, or as the argument to .each().
$('.element').each( handler_func );
...as long as you don't need the event object.
EDIT: Or if you want the values returned from the invocation, use .map() instead:
var return_values = $('.element').map( handler_func );
EDIT: With respect to the example provided in the updated question, a good solution may be to take advantage of the extraParameters capability of the trigger()[docs] method so that you can tell the handler to preventDefault() and stopPropagation().
$('.elememts').bind( 'click', function( e, was_code_triggered ) {
if( was_code_triggered ) {
e.preventDefault();
e.stopPropagation();
}
// your code
});
// ...
$('.elememts').trigger( 'click', true ); // pass "true" to let handler know
// it wasn't a DOM event
From the .trigger() docs:
"Note the difference between the extra parameters we're passing here and the eventData parameter to the .bind() method. Both are mechanisms for passing information to an event handler, but the extraParameters argument to .trigger() allows information to be determined at the time the event is triggered, while the eventData argument to .bind() requires the information to be already computed at the time the handler is bound."

JavaScript watch event not working on DOM element objects?

I know I can use watch to bind a callback that will be triggered when object property changes. And this does work on generic objects like:
{'a':1, 'b':'7'}
So, I thought that I can simply do this to bind a callback that will trigger when input field value changes:
var inputDomElement = document.getElementById('someInputElement');
inputDomElement.watch('value',function (id, oldval, newval) {
alert(oldval);
alert(newval);
});
But this doesn't work. Simply doesn't trigger. No alert boxes. I've tried it in Firefox 5 and Google Chrome (latest).
Is this not how watch works? Is watch simply doesn't work on DOM elements? I thought that they're simply objects - aren't they?
UPDATE 1:
Here's MDN info about what watch is:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/watch
UPDATE 2:
I cannot use change event. because change only triggers when text element catches blur. Meaning that it'll only trigger when user switches from this textfield to another one. It's not in any way dynamic for when for example checking if this username or email address already taken which I'd like to happen on each distinct change.
The DOM is written in C/C++ where the concept of getting and setting a Javascript variable doesn't exist as you or I would often imagine it. You probably imagined the code to be implemented similar to what is below. Unfortunately Object.watch is never initiated because the DOM isn't constantly updating the Javascipt value, but Javascript is requesting an update from the DOM.
input.onuserchangevalue = function(){
input.value = 'new user input'
}
Thinking how the DOM commonly works, each element has dozens of potential properties.
innerHTML,value,style.cssText,name,id,style.background,style.backgroundColor
Imagine if the DOM underlining code had to constantly update every DOM elements Javascript properties %) Memory and CPU cycles would go through the roof having to ensure the properties matched the display value. The DOM's internals would also have to check if the Javascript value has potentially changed.
Reality - Red Pill
Basically the DOM isn't giving info to javascript, but Javascript is requesting the info from the DOM. This is a decent Javascript interpretation of what is going on underneath.
Object.defineProperty(input, "value", {
get : function(){ /* get C/C++ DOM value */ },
set : function(){ /* change C/C++ DOM value */ }
});
This explains why the DOM is often the bottleneck for Javascript. Javascript has to request/set the internal DOM values every time you interface with the DOM.
You need to use jQuery Objects, not DOM Objects. There is a difference.
document.getElementById("someID") //returns DOM Object
$('#someId') //returns jQuery Object
Note: you could doe something strange like this:
$(document.getElementById("someID")) //returns a jQuery Object
this would work
$('#someInputElement').watch('value',function (id, oldval, newval) {
alert(oldval);
alert(newval);
});
if you want to track changes on a text element, why not just use the .change() method?
http://jsfiddle.net/rkw79/qTTsH/
$('input').change(function(e) {
$('div').html('old value: ' + e.target.defaultValue + '<br/>'
+ 'new value: ' + e.target.value);
})
For instant change, use .keyup(): http://jsfiddle.net/rkw79/qTTsH/1/

Using $.data() in jQuery to store flag on element

I am authoring a simple jQuery plugin that turns an input tag into a time-formatted element (on blur it will change 245p into 2:45 pm).
Since I do not want to apply the time format events to the same element twice, I need a way to detect that the specific element in the list provided has not already had the format applied.
This is the relevant part of the code:
var methods = {
init : function(sel) {
var $this = $(sel);
return $this.each(function(){
var data = $(this).data('time_formatted');
if (data) {
return;
} else {
$(this).data('time_formatted', true);
I have heard that using $(sel).data() in a plugin is not a good idea; instead, use $.data(). I don't know why, that's just what I've heard; honestly, I don't know what the difference is.
So my question is, is this the way to accomplish checking if a specific element has had the time formatter applied to it in a plugin?
If you care to see the plugin in it's current development state, see http://jsfiddle.net/userdude/xhXCR/.
Thanks!
Jared
Where have you heard that using .data() is not good? jQuery's plugin autoring page says:
Often times in plugin development, you may need to maintain state or check if your plugin has already been initialized on a given element. Using jQuery's data method is a great way to keep track of variables on a per element basis. However, rather than keeping track of a bunch of separate data calls with different names, it's best to use a single object literal to house all of your variables, and access that object by a single data namespace.
So it should be perfectly fine.

Rendering suggested values from an ext Combobox to an element in the DOM

I have an ext combobox which uses a store to suggest values to a user as they type.
An example of which can be found here: combobox example
Is there a way of making it so the suggested text list is rendered to an element in the DOM. Please note I do not mean the "applyTo" config option, as this would render the whole control, including the textbox to the DOM element.
You can use plugin for this, since you can call or even override private methods from within the plugin:
var suggested_text_plugin = {
init: function(o) {
o.onTypeAhead = function() {
// Original code from the sources goes here:
if(this.store.getCount() > 0){
var r = this.store.getAt(0);
var newValue = r.data[this.displayField];
var len = newValue.length;
var selStart = this.getRawValue().length;
if(selStart != len){
this.setRawValue(newValue);
this.selectText(selStart, newValue.length);
}
}
// Your code to display newValue in DOM
......myDom.getEl().update(newValue);
};
}
};
// in combobox code:
var cb = new Ext.form.ComboBox({
....
plugins: suggested_text_plugin,
....
});
I think it's even possible to create a whole chain of methods, calling original method before or after yours, but I haven't tried this yet.
Also, please don't push me hard for using non-standard plugin definition and invocation methodics (undocumented). It's just my way of seeing things.
EDIT:
I think the method chain could be implemented something like that (untested):
....
o.origTypeAhead = new Function(this.onTypeAhead.toSource());
// or just
o.origTypeAhead = this.onTypeAhead;
....
o.onTypeAhead = function() {
// Call original
this.origTypeAhead();
// Display value into your DOM element
...myDom....
};
#qui
Another thing to consider is that initList is not part of the API. That method could disappear or the behavior could change significantly in future releases of Ext. If you never plan on upgrading, then you don't need to worry.
So clarify, you want the selected text to render somewhere besides directly below the text input. Correct?
ComboBox is just a composite of Ext.DataView, a text input, and an optional trigger button. There isn't an official option for what you want and hacking it to make it do what you want would be really painful. So, the easiest course of action (other than finding and using some other library with a component that does exactly what you want) is to build your own with the components above:
Create a text box. You can use an Ext.form.TextField if you want, and observe the keyup event.
Create a DataView bound to your store, rendering to whatever DOM element you want. Depending on what you want, listen to the 'selectionchange' event and take whatever action you need to in response to the selection. e.g., setValue on an Ext.form.Hidden (or plain HTML input type="hidden" element).
In your keyup event listener, call the store's filter method (see doc), passing the field name and the value from the text field. e.g., store.filter('name',new RegEx(value+'.*'))
It's a little more work, but it's a lot shorter than writing your own component from scratch or hacking the ComboBox to behave like you want.
#Thevs
I think you were on the right track.
What I did was override the initList method of Combobox.
Ext.override(Ext.form.ComboBox, {
initList : function(){
If you look at the code you can see the bit where it renders the list of suggestions to a dataview. So just set the apply to the dom element you want:
this.view = new Ext.DataView({
//applyTo: this.innerList,
applyTo: "contentbox",
#qui
Ok. I thought you want an extra DOM field (in addition to existing combo field).
But your solution would override a method in the ComboBox class, isn't it? That would lead to all your combo-boxes would render to the same DOM. Using a plugin would override only one particular instance.

Categories

Resources