Problems unwatching an object using watch.js - javascript

I'm trying to use watch.js in the code below. The idea is too run watch.js in a one shot way, so it detects the boolean change (of vars.in_animation) and then unwatchs and exits. However, what happens is the code falls into inifinite loop and everytime vars.in_animation changes the watch callback is triggered and the unwatch code never runs.
I don't know whether the problem is the way I'm using watch.js, or if there is some other issue in the structure of the code. Any ideas?
base.goTo = function(targetSlide){
if (vars.in_animation || !api.options.slideshow)
{
//arrange for a callback once in_animation is false again
//only need to detect first change from true to false, and then unwatch
watch(vars, "in_animation", function()
{
console.log('called back, value of in_animation ' + vars.in_animation);
unwatch(vars,"in_animation",vars.alert); //never called
base.goTo2(vars.saved_slide);
});
vars.saved_slide = targetSlide;
return false;
}
else { base.goTo2(targetSlide); }
}
EDIT, just made a fiddle of the problem > http://jsfiddle.net/SZ2Ut/3/
var obj = {
name: "buddy",
alert: function(){
alert(obj.phrase + " " + obj.name);
}
}
watch(obj, "name", function(){
alert('name has changed to: ' + obj.name);
});
//should only fire once for this one
obj.name = "johnny";
unwatch(obj, "name", obj.alert);
obj.name = "phil";

unwatch seems to expect the watcher callback that should be removed as its third argument. Try
watch(vars, "in_animation", function callback() {
console.log('called back, value of in_animation ' + vars.in_animation);
unwatch(vars,"in_animation", callback);
base.goTo2(vars.saved_slide);
});

Related

Accessing a JavaScript variable from a namespace at a "global" level on the same page

I'm trying to access a variable from a namespace on the same page at a "global" level but thus far have been unable to do so. Here's the code:
window.addEventListener('resize',
() => {
alert('entityId in addEventListener alert: ' + Best.Namespace.Ever.entityId);
// prints "entityId in addEventListener alert: undefined"
});
var Best = Best || {};
Best.Namespace = Best.Namespace || {};
Best.Namespace.Ever= Best.Namespace.Ever || (function () {
var entityId;
function doSomethingAmazing(something) {
entityId = alterEntityIdBasedUponSomething(something);
alert('entityId in doSomethingAmazing: ' + entityId);
// prints "entityId in doSomethingAmazingalert: 14"
// a lot of other stuff
}
return {
doSomethingAmazing: doSomethingAmazing,
entityId: entityId
// a lot of other stuff
};
}());
The issue here is that I'm unable to access the entityId from within the alert in the addEventListener method. At that point it is undefined. However, I can access it in the alert just after it is set just fine. (See the comments in the code above for what prints.)
I feel like this should be possible and I just don't know the correct way to access entityId. If anyone has any ideas I would very much appreciate it.
Thanks!!
Your problem is that primitives are passed as values and not as references in JS.
Here is what you want:
window.addEventListener('resize',
() => {
alert('entityId in addEventListener alert: ' + Best.Namespace.Ever.wrapper.entityId);
// prints "entityId in addEventListener alert: 14"
// after 1 second
// prints "entityId in addEventListener alert: 18"
});
var Best = Best || {};
Best.Namespace = Best.Namespace || {};
Best.Namespace.Ever= Best.Namespace.Ever || (function () {
const wrapper = {entityId : 14} ;
function doSomethingAmazing(something) {
//entityId = alterEntityIdBasedUponSomething(something);
wrapper.entityId = 18;
alert('entityId in doSomethingAmazing: ' + wrapper.entityId);
// prints "entityId in doSomethingAmazingalert: 18"
// a lot of other stuff
}
return {
doSomethingAmazing: doSomethingAmazing,
wrapper
// a lot of other stuff
};
}());
setTimeout(_=>{
Best.Namespace.Ever.doSomethingAmazing();
},1000)
As it turns out this wasn't exactly an issue with the code, but rather with unexpected page loading/event firing. What was happening is that the doSomethingAmazing function was being called on page load and was setting the entityId to 14 just fine. However, after that another event (or something) was reloading the entire script again which was re-initializing entityId back to undefined (but NOT calling doSomethingAmazing). So when the resize event was called and entityId was printed it was undefined.
My problem here was a failure to understand the basic principle that the entityId is essentially stateless between JavaScript page loads; it gets re-initialized every time the entire script is loaded.
Hopefully this helps someone!

Is there a simple way to invoke a function inside in an event with jQuery?

I hope to invoke the function inside the event $('.CssDeleteFolder').click(function () {} in the function cc() in Code 1.
The Code 2 can do it, but I think it's too complex, is there a simple way to do it?
Code 1
function cc(){
//How to invoke $('.CssDeleteFolder').click(function () event
}
$('.CssDeleteFolder').click(function () {
var fileName = GetHiddenFilename(this);
var defaultname = fileName.replace(/^.*[\\\/]/, '');
defaultname = decodeURIComponent(defaultname);
if (confirm('Do you want to delete the folder ' + defaultname + ' and all the sub-folders ?')) {
location.href = "actiondeletefolder.htm?fullfilename=" + fileName + "&origurl=" + GetOrigUrl() + AdditionalURL();
}
});
Code 2
function aa(){
//Do something
bb();
}
$('.CssDeleteFolder').click(function () {
bb();
});
function bb(){
var fileName = GetHiddenFilename(this);
var defaultname = fileName.replace(/^.*[\\\/]/, '');
defaultname = decodeURIComponent(defaultname);
if (confirm('Do you want to delete the folder ' + defaultname + ' and all the sub-folders ?')) {
location.href = "actiondeletefolder.htm?fullfilename=" + fileName + "&origurl=" + GetOrigUrl() + AdditionalURL();
}
}
You can use $('.CssDeleteFolder').click() or synonymous $('.CssDeleteFolder').trigger("click") to trigger click event.
However, for me it seems like a bad code smell.
If your function provides a reusable behavior, it is a much better idea to use a named function and bind it instead of triggering an event with anonymous handler.
I would recommend using second approach if this function needs to be called (or has a chance that it will need to be called) from other places. You may also want to use a non-global context (f.i., using IIFE) to not pollute global namespace.
By the way, you can do:
$('.CssDeleteFolder').click(bb);
instead of
$('.CssDeleteFolder').click(function () {
bb();
});
This is very easy and simple. I assume that you are using jquery. You can invoke click event of any element like:
$('.CssDeleteFolder').trigger('click'); // This will fire the click event manually.
You can trigger any event you have attached to your control by using the following syntax:
$('YourSelector').trigger('eventName');
This is what you asked for:
function cc(){
$('.CssDeleteFolder').click();
}
$('.CssDeleteFolder').click(function () {
var fileName = GetHiddenFilename(this);
var defaultname = fileName.replace(/^.*[\\\/]/, '');
defaultname = decodeURIComponent(defaultname);
if (confirm('Do you want to delete the folder ' + defaultname + ' and all the sub-folders ?')) {
location.href = "actiondeletefolder.htm?fullfilename=" + fileName + "&origurl=" + GetOrigUrl() + AdditionalURL();
}
});

Modifying number of arguments for callback functions - javascript

I know many of you already used JavaScript UI widget plugins, etc... that offers callback functions. For instance we have Object x and it has a function, let say .doThisAfterAnEvent(). And according to the official documentation of Object x, that function accepts a single parameter of type function() with one argument, let say _args.
To visualize, here is the example:
var handler = function(_args) {
// Do something.
}
var x = $("#element-to-widget-ify").transform()
x.doThisAfterAnEvent(handler)
My question is, how can I modify the method .doThisAfterAnEvent() to accept a function with two or more parameters instead of one? In this case, I need to pass a second extra value to the handler function.
Edit:
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform(),
length: 3
}
var handler = function(_args, _option) {
console.log("key in: " + _option
// Other processes...
}
for(key in widgets) {
console.log("key outer: " + key)
widget[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key)
handler(json, key)
})
}
This is my attempt. But it prints like this:
key outer: widget-at-the-nav
key outer: widget-at-the-footer
key outer: widget-at-the-search
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
key out: widget-at-the-nav
key in: widget-at-the-nav
And I forgot to tell you guys that the function .doThisAfterAnEvent() (not the handler() function) has an AJAX call inside.
This question is a mess, so I'm only going to touch on the most recent edit, and the code it contains.
Your approach with masking the handler with an anonymous function is pretty much correct, it's just that your loop is messing up your lexical scope. The AJAX bit is a very important detail, because any AJAX calls are most likely going to operate long after you've looped, which means those callback functions will all reference the same final value of key.
You need to create a new scope where the key is 'locked in', so that the references are correct.
function Con () {}
Con.prototype.doThisAfterAnEvent = function (fn) {
// Fake some AJAX programming
window.setTimeout(function () {
fn.call(this);
}, 1500);
};
$.fn.transform = function () {
return new Con();
};
var widgets = {
"widget-at-the-nav": $("#nav-widget").transform(),
"widget-at-the-footer": $("#nav-footer").transform(),
"widget-at-the-search": $("#nav-search").transform()
};
var handler = function(_args, _option) {
console.log("key in: " + _option);
// Other processes...
};
for (var key in widgets) {
console.log("key outer: " + key);
(function (key) {
// Use an IIFE to establish a new lexical scope
widgets[key].doThisAfterAnEvent(function(json) {
console.log("key out: " + key);
handler(json, key);
});
}(key));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
In ES6, we'd use let.
If you ask that, I guess you mean at the moment you call doThisAfterAnEvent, you already know one parameter over two for your handler.
In this case, the solution is too wrap your handler with two parameter in a anonymous function that only take one parameter and then call back your handler :
x.doThisAfterAnEvent(function(_arg) { handler(myKnownArg, _arg) });

another way then the function stacking in Javascript

I am looking for a better method of doing something that is common in JavaScript. How do I do this:
// global namespace
var PlexLib = PlexLib || {};
PlexLib = {
FileName : "",
ShowName : "",
AddFileSelector_result : function (data,status,xhr) {
console.log ("Callback here." + data);
console.log ("Callback here." + status);
console.log ("Callback here." + xhr);
},
AddFileSelector : function () {
var afs = this.AddFileSelector_result();
$.get(script_name ,"AddFileSelector=AddFileSelector",afs,"html");
},
RemoveFileSelector : function () {
console.log ("Do Remove.");
},
}
Ok, at $.get(script_name ,"AddFileSelector=AddFileSelector",afs,"html")
the problem is with afs.
The specification of .get request is:
$.get(URL,data,function(data,status,xhr),dataType)
What I am getting up above is when the callback occurs, I am either getting 'undefined' in the data, (data,status,xhr) or, the entire script passed in. I have no idea how that's happening either.
So, what I am asking is, when I see a definition like:
$.get(URL,data,function(data,status,xhr),dataType)
How do I do the function(data,status,xhr) part, I want it to be a reference to a normal:
function () {
}
defined someplace else.
Don't call the function here:
var afs = this.AddFileSelector_result();
Pass it as a pointer:
var afs = this.AddFileSelector_result;
so that when the AJAX request completes it will be invoked with the proper arguments.

Why assigning the same value triggers subscribe handler?

I have a simple viewmodel with an observalbe array of items, and an observable holding the selected item. I subscribe to the changes of selected item, and I can see in my tests that the handler is fired even when I assign the same value again and again, so there should not be any change. The following code shows 3 alerts with all the same "changed to ..." text.
view.SelectedItem(view.Items()[0]);
view.SelectedItem.subscribe(function(newValue) {
alert("changed to " + ko.toJSON(newValue));
});
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
view.SelectedItem(view.Items()[0]);
Here is a demo fiddle.
Apparently, selecting an item, even if it's the same one as what's already selected, triggers the change event, calling the function specified when subscribing.
If you want to be notified of the value of an observable before it is about to be changed, you can subscribe to the beforeChange event. For example:
view.SelectedItem.subscribe(function(oldValue) {
alert("The previous value is " + oldValue);
}, null, "beforeChange");
Source
This could help you determine whether or not the value has changed.
You can create function to have access to old and new values for compare it:
ko.subscribable.fn.subscribeChanged = function(callback) {
var previousValue;
this.subscribe(function(oldValue) {
previousValue = oldValue;
}, undefined, 'beforeChange');
this.subscribe(function(latestValue) {
callback(latestValue, previousValue);
});
};
You could add this function to some file with you ko extensions. I once found it on stackoverflow but can't remeber link now. And then you could use it like this:
view.SelectedItem.subscribeChanged(function(newValue, oldValue) {
if (newValue.Name != oldValue.Name || newValue.Quantity != oldValue.Quantity) {
alert("changed to " + ko.toJSON(newValue));
}
});
Fiddle
I ended up creating my own method based on a thread on a forum:
// Accepts a function(oldValue, newValue) callback, and triggers it only when a real change happend
ko.subscribable.fn.onChanged = function (callback) {
if (!this.previousValueSubscription) {
this.previousValueSubscription = this.subscribe(function (_previousValue) {
this.previousValue = _previousValue;
}, this, 'beforeChange');
}
return this.subscribe(function (latestValue) {
if (this.previousValue === latestValue) return;
callback(this.previousValue, latestValue);
}, this);
};

Categories

Resources