"Function factory" doesn't seem to work, JavaScript - javascript

I'm displaying a series of images in a loop, and I'm trying to implement some sort of nudity filter so I'm using nude.js, a library that can somewhat detect nudity. Here's the code:
// we're inside a loop
$(".images").prepend($("<img>").attr({src: whatever, id: uniqueid}).load(function(e) {
nude.load(e.target.id);
nude.scan(function(result) { if (!result) $(e.target).detach(); });
});
However, it detaches all of the wrong images because nude.js is slow and it completes after the loop has gone on to the later iterations, detaching those images instead of the one it was working on.
I've tried using a function factory:
function generateCallback(arg) {
return function(result) { if (!result) $(arg).detach(); };
}
and
nude.scan( generateCallback(e.target) )
but the same thing happens.
What I want is a load event that will remove the image if it seems to contain nudity. How can I do this properly?
EDIT: nude.js works like this:
nude.load(imageid);
nude.scan(callback); // it'll pass true or false into the callback
another edit: accidentally omitted the id setting from the code I posted, but it was there in my real code, so I added it here.

I suspect the case here is that this kind of sequential processing won't work with nude.js.
Looking at the nude.js code, I think your problem is occurring in the call to nude.scan. nude.js has a variable that stores the function to invoke after the scan has completed. When calling nude.scan(callback), this variable is set to be callback.
From your PasteBin, it seems as though the callback gets assigned as expected on the first call, but on the second and subsequent calls, it gets replaced, hence why the second image is detached and not the first.

What happends to your script, is that the e var is global to the function and so after each loop it gets replaced with the new one. So when the first image is scanned, e already became the event of the second image, which get detached.
To solve your problem, use closures. If you want to know more about closures, have a look here.
Otherway, here's the solution to your problem :
$(".images").prepend($("<img>").attr({src: whatever, id: uniqueid}).load(function(e) {
(function(e) {
nude.load(e.target.id);
nude.scan(function(result) { if (!result) $(e.target).detach(); });
}) (e);
});
EDIT: AS nick_w said, there is var that contains the callback and probably gets replaced each time so this is why it isn't the right picture getting detached. You will probably have to modify the script yourself

Related

Meteor Method Endlessly Runs

I have this code in a Meteor.methods definition:
update_field: function(collection,document_id,field,value) {
obj = {};
obj[field] = value;
console.log(obj);
if (collection == 'clients') {
var Collection = Clients;
} else if(collection = 'sites') {
var Collection = Sites;
}
Collection.update(
{
_id: document_id
}, {
$set: obj
}, function(error,id) {
console.log(error,id);
return(error,id);
}
);
}
This method is called from several client-side helpers events, and updates the field as needed. But whenever it runs once, it never stops running. Sometimes it runs infinitely even when all the Meteor.call('update_field')s have been commented out. I have tried including a 'caller' parameter and adding that to all the possible calls to figure out why it keeps getting called to no avail. Any ideas why this is looping?
Edit: this runs 2,000/minute
Edit2: this is called in one of two ways: on a keyup code==13 (enter) in an appropriate field or a field blur. However, event when these calls are commented out, the issue persists.
Especially your second comment worries me:
However, even when these calls are commented out, the issue persists.
Then who is calling it? The behaviour you're describing points to some helper executing the method. The method changes some data, which re-executes the helper (reactivity) and we end up with a classic endless loop.
Check your entire source code for references to this method:
$ grep -r "update_field" *
Maybe you set a variable somehow and then use the variable to call the method. Also: Have you declared the method inside a Meteor.methods({ ... }) block?
I think the issue was that one of my methods blurred the input field but preventDefaulted. Then the blur handler was called and caused the loop from there. This is my first Meteor project, so I'm chalking this one up to not quite understanding the system sufficiently. I still find it strange that the method was getting called when the callers were commented out, but I'll figure that one out another day.

Javascript: Variable scope and usage order

I came across a peculiar issue when trying to make an ajax call and isolate the actions of the function to itself. Here is the code snippet
$(document).on('click', 'input.action', function(event) {
var self = this;
$.ajax({
url:'http://date.jsontest.com/',
method:'GET',
cache:false,
dataType:'json',
success:self.process,
error:function(){self.process(false);}
});
self.process = function(data) {
if (data) {
alert(data.time);
}
else {
alert("Operation Failed!");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="container">
<input type="button" value="Get Time" class="action"/>
</div>
Let me briefly explain what I am trying to do, on click of the button, I wish to receive some data from the server and display the data. I am using a process function to process the received data. In case of an error I reuse the process function in a different way to display the error message. I simply use the self variable to contain all the elements within the parent function. I fully understand the following:
What I know
I do not have to use the self to contain the process function because another method will not have access to it
Because the process method in the snippet above is declared after the ajax call for the program as far as it is concerned the process function is undefined.
I clearly know how to fix it.
Experiment:
Click on the Get Time button
Wait for as long as you want but see no result, which is expected because of the process function is declared after the ajax call
Click on the Get Time button again
It works now! Some time (which is probably not your time :P) is displayed now!!
What I wish to know:
What just happened? why does it work the second time and everytime after? Remember this only works for ajax calls, if it were the case that the assignment is retained in the function after calling it once, then this should work in every situation but it does not. Here is an experiment to show that it does not work the same way when ajax calls are not used: Fiddle - Experiment
The Solution:
I am adding a sample solution based on #Felix Kling's answer below. In the Sample Solution, there are two buttons Get Time and Get Date. I've attached the parameter to retrieve time in case of Get Time and date in the case of Get Date to the object self and it is interesting that once I click on Get Time nothing happens just like before but if I click on either Get Time or Get Date the second time only time is displayed.
What just happened?
In a simplified way, this:
var process;
// First click
ajaxCall(process); // process is still undefined
process = function() { ... };
// second click
ajaxCall(process); // process is defined
process = function() { ... };
The assignment to self.process "persists" between events because self refers to the same element.
Here is an experiment to show that it does not work the same way when ajax calls are not used: ...
It doesn't work in your fiddle because of one big difference: you are trying to execute process immediately. But it doesn't exist, so an error is thrown. At this point the script terminates and won't execute the rest of the function, where the function definition takes place.
In your example here, the execution of process is delayed. The script "doesn't know" that there is no function to call until the response was received.
There is nothing "special" going on here. Yes, accepted that the first time it returns undefined as it is yet to be defined.
But, you're attaching the function to the same element input.action. So, the next time, when you click the button, this already has the process method attached to it, so gets called when clicked again.
Try adding one more button with same class and click each once. Now though you've clicked the first button, clicking the second button will still not create an alert as it has not yet had the process function attached to it.

Is jQuery Infinite nesting bad for performance?

// page load
InitTeacherLinks()
function InitTeacherLinks()
{
$(".open-ungraded-test").click(function()
{
$.post("class_viewer.php", {
_open_lesson_direct : 1
}, function(data)
{
$("#content_display").html(data);
InitGradingActions(test_taken_id); // Notice this Call
});
})
}
function InitGradingActions(test_taken_id)
{
$("#save_grading").click(function()
{
$.post("class_viewer.php", {
_save_graded_test : 1
}, function(data)
{
$("#content_display").html(data);
InitTeacherLinks(); // Is this Circular logic?
});
});
}
Basically, I have a div called content_display that shows a list of tests. After I load it full of tests, I have to make each test link clickable. So I do that in this function: InitTeacherLinks() where they can view an individual test.
Well the user can exit the test and go back to the original test list. So I have to call the parent function again in the child function.
While this DOES work, I notice I do it often. Is this bad logic or bad for performance?
Note: I can only think of one possible reason why this may work. Please correct me if I am wrong. when save_grading is clicked, it effectively destroys reference to the original (parent function) so rather than creating a duplicated reference, we are simply reinitialize it. Is this right?
I don't think there's a stack overflow issue with the code, but it does look like there may be an error. Every time InitTeacherLinks() is executed, a new click handler is assigned to .open-ungraded-test. That means there is an additional ajax post made during that click for every time InitTeacherLinks() is run, which could be a lot.
At least that's how it looks from the code. This could depend on the structure of your document.
I ended up not changing anything and went with what I have above.
Because the click events are unbinded every time the element is destroyed, this was not creating an endless loop (or Stackoverflow error) which was my concern. The code in the question is correct.

Angularjs chaining .then to depend on each other

I have a chain of events set in .then statements. I wish I understood a way to use .when() in this case. This function is called on a ngn-click. The issue is that I have to click twice for it to go through. The $rootScope.csa has data going into the function that is used in the .then( functions ). I when in inspect in the chrome debugger the and step through everything works fine I believe it is because the debugger is slowing down the application and allowing it to keep up with its self. Other wise when I go through with out the debugger it goes so fast that it takes two clicks for $rootScope.csa.section.data to be populated for the next function to work as expected. The first two then statement functions are services that are wrapped in a promise and $timeout on there end and the $timeouts do not seem to be delaying the process. I have looked over q.derer() many times but cannot wrap my head around how it would be implemented in this case. Any help or information to get to the needs that I am looking for would ne appreciated.
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then($rootScope.csa = audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
.then($rootScope.csa = audit.GetInstance($rootScope.csa), function(){ return $rootScope.csa}) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
.then(function() {if($rootScope.csa.section.data) $location.path('/Update')})
.then(function() {if($rootScope.csa.section.data) $rootScope.$broadcast('root_updated')
});
You always need to pass a callback function to then, not some call result (even if it is a promise). I have not wrapped my head around what these functions do, but try
audit.LockData('section', $scope.csa.ID, user.ID, $scope.csa.Revision)
.then(function(lockResult) {
return audit.RecordsCheck($rootScope.csa)) // gets data and pupulates it to the $rootscope.csa.section.data
})
.then(function(checkResult) {
return audit.GetInstance($rootScope.csa) // gets ID(s) from $rootScope.csa.section.data.instances.ID(s) and populates them to the $rootScope.csa.section.instances
})
.then(function(instance) {
if ($rootScope.csa.section.data) {
$location.path('/Update')
$rootScope.$broadcast('root_updated')
}
});

innerHTML can't be trusted: Does not always execute synchronously

To see the problem in action, see this jsbin. Clicking on the button triggers the buttonHandler(), which looks like this:
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking";
longPrimeCalc();
}
You would expect that this code changes the text of the div to "thinking", and then runs longPrimeCalc(), an arithmetic function that takes a few seconds to complete. However, this is not what happens. Instead, "longPrimeCalc" completes first, and then the text is updated to "thinking" after it's done running, as if the order of the two lines of code were reversed.
It appears that the browser does not run "innerHTML" code synchronously, but instead creates a new thread for it that executes at its own leisure.
My questions:
What is happening under the hood that is leading to this behavior?
How can I get the browser to behave the way I would expect, that is, force it to update the "innerHTML" before it executes "longPrimeCalc()"?
I tested this in the latest version of chrome.
Your surmise is incorrect. The .innerHTML update does complete synchronously (and the browser most definitely does not create a new thread). The browser simply does not bother to update the window until your code is finished. If you were to interrogate the DOM in some way that required the view to be updated, then the browser would have no choice.
For example, right after you set the innerHTML, add this line:
var sz = elm.clientHeight; // whoops that's not it; hold on ...
edit — I might figure out a way to trick the browser, or it might be impossible; it's certainly true that launching your long computation in a separate event loop will make it work:
setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
A good lesson here is that browsers try hard not to do pointless re-flows of the page layout. If your code had gone off on a prime number vacation and then come back and updated the innerHTML again, the browser would have saved some pointless work. Even if it's not painting an updated layout, browsers still have to figure out what's happened to the DOM in order to provide consistent answers when things like element sizes and positions are interrogated.
I think the way it works is that the currently running code completes first, then all the page updates are done. In this case, calling longPrimeCalc causes more code to be executed, and only when it is done does the page update change.
To fix this you have to have the currently running code terminate, then start the calculation in another context. You can do that with setTimeout. I'm not sure if there's any other way besides that.
Here is a jsfiddle showing the behavior. You don't have to pass a callback to longPrimeCalc, you just have to create another function which does what you want with the return value. Essentially you want to defer the calculation to another "thread" of execution. Writing the code this way makes it obvious what you're doing (Updated again to make it potentially nicer):
function defer(f, callback) {
var proc = function() {
result = f();
if (callback) {
callback(result);
}
}
setTimeout(proc, 50);
}
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking...";
defer(longPrimeCalc, function (isPrime) {
if (isPrime) {
elm.innerHTML = "It was a prime!";
}
else {
elm.innerHTML = "It was not a prime =(";
}
});
}

Categories

Resources