Inconsistent jQuery each() behavior with unexplainable fix - javascript

I'm writing a plugin based on this handy template for class-based CoffeeScript jQuery plugins: https://gist.github.com/rjz/3610858
Everything works pretty well. However, there is some unexpected behavior at the end when I register the plugin:
$.fn.extend markdownAsides: (option, args...) ->
#each ->
$this = $(this)
data = $this.data('markdownAsides')
if not data?
$this.data 'markdownAsides', (data = new MarkdownAsides(this, option))
if typeof option is 'string'
data[option].apply(data, args)
data # Plugin breaks without this line
Before I added that final line (a solution I discovered purely on accident), the initial construction of the plugin worked fine, but on successive method calls, the jQuery each loop sometimes failed to iterate through every element.
Checking this.size() outside the each loop returned the correct value, and checking individual elements outside the loop also looked fine. But inside the loop, elements would sometimes be skipped, in a pattern I could not discern.
Like I said, the problem is fixed by adding the final line. (Perhaps the return value of the function being passed to each matters somehow?) My question isn't "how do I fix this?" but "why does this work?"

Returning false from the callback function passed to each will break out of the loop. I haven't verified but perhaps jQuery will also break on any falsey value except undefined.
Since in CoffeeScript there's an implicit return, you were possibly returning something falsey or even false from the callback depending on the operation performed in it.
To avoid any issues, just change data for true at the end.

Related

Correct javascript syntax for function

I've been looking to at some old javascript on a website I run - hands up, I know very little about javascript.
I came across the following function:
var timeout = null;
function textareaResize() {
if (typeof(timeout))
clearTimeout(timeout);
timeout = setTimeout(function () {
refreshAutoGrowFields();
}, 200);
}
The intellisense tells me that the syntax typeof(timeout) is invalid, to be more specific 'unexpected constant condition', however this means nothing to me.
I'd appreciate it if someone could explain what should actually be there (if the intellisense is correct in its assumptions).
I'm not sure where that function was found, but it's definitely confusing, because it's actually inaccurate JavaScript.
What it's trying to do is make whats called a timeout, meaning a performance of a certain action or collection of code after a period of time.
In order to do this, it's attempting to set this "timeout" function to a pre-defined variable called "timeout".
The thing is, before they are setting it to the variable "timeout", for some reason they want to make sure that timeout hasn't already been set. Perhaps by setting it twice to setTimeout, it would potentially cause multiple functions to happen within the same amount of time (but perhaps not).
Anyways, in order to do that, they were trying to check if "timeout" has been set before.
The problem is the way they are checking it typeof(timeout), is completely incorrect, for 2 reasons.
First, the term "typeof" isn't usually to be used like a function call; rather, it is a keyword, much like var or return, so it shouldn't in general be used with ()s like it's used here, instead it should be used like typeof timeout == "null", but the problem is that even such a syntax would be completely invalid, because even if it was null, it wouldn't evaluate to "null", it would evaluate to "object", just try pasting the following JavaScript into a console:
typeof null
It should say "object", even typeof undefined is "undefined", which is a string, which, when put in an if statement, would still evaluate to true relatively speaking, so the if statement checking if timeout is defined or not was done completely wrong.
Instead, that if statement should simply be changed to if(timeout), removing typeof completely.
typeof will return the type of the variable you put inside.
These are some types in JavaScript
number
string
boolean
array
object
undefined
you can use the type as
let s1 = 'somevalue'
cosole.log(typeof(s1)) // prints 'string'
you can play around with that and change the contents of s1. Now to the comparing part, if you try something like
if(typeof(s1)=='number')
dothis()
else if (typeof(s1)=='string')
dothat()
tip : both are same
typeof s1
typeof(s1)
and typeof(typeof(someVar)) is always string, that's why the == operator works
Hope it helped and didn't confuse you further.

Functional use of Array.includes causes TypeError [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 23 days ago.
I would expect [1].some([1].includes) to return true, instead I get an error saying:
Uncaught TypeError: Cannot convert undefined or null to object.
Any ideas what would cause this? As I understand, Array.some accepts a function invoked per array item, which [1].includes should fulfill.
You lose the context when you pass that function as is: when it's invoked as a callback, value of this inside it is undefined in strict mode (and global object in non-strict mode), not [1]. To address this issue, you may fix the context:
[1].some([].includes.bind([1]))
Note that it doesn't matter which array is used to access includes function; you might as well write that as...
[1].some( Array.prototype.includes.bind([1]) )
That's be a bit less concise, but a bit more efficient (as no immediate array is created). Still, it almost never should be a bottleneck; thus you should better optimize for readability.
Unfortunately, this won't be enough. See, Array.includes() uses two parameters:
arr.includes(searchElement[, fromIndex])
... and Array.some() does supply it with those two (even three in fact, but only two are used by includes). That's why this...
[1,2,3].some([].includes.bind([1])); // true
... works, but this...
[2,1,3].some([].includes.bind([1])); // false
... doesn't: the lookups in [1] array start from 0th, 1st, 2nd elements - and apparently fail after the first one.
To fix this, you might either create a function that takes exactly one argument with something like lodash's _.unary:
[2,1,3].some(_.unary([].includes.bind([1]))) // now we're talking!
... or bite the bullet and use arrow function instead. Note that you can still use a function with a bound context here:
const checker = [].includes.bind([1]);
[2,1,3].some(el => checker(el));
... to make this more flexible.
It depends on the specific implementation of includes in whatever JS engine you are using, but usually the standard library functions are not very co-operative for point-free style programming.
Generally this is because the context (this) is not assigned as intended. This can be shown by trying the following:
[1].some([1].includes); // Error
[1].some([1].includes.bind([1])) // true
Edit: This answer is not entirely correct. You should probably read rain77ow's answer above

forms.$valid showing true, but prints out as false

I have a strange error and I am not sure what's happening.
When I check an object, I can see that the $valid tag is set to true (and $invalid shows false). But when I print out just that tag (such as object.$valid) it prints false (and object.$invalid prints true).
$scope.$watch('ctrl.form', form => {
console.log('The form is set to: ', form);
console.log('The form is valid: ', form.$valid);
});
Does anyone know why this might be happening?
I suspect the issue is, the values are still updating in that function; it is watching for a change in the value, but still hasn't applied the changed values. This can make your code in that handler unpredictable.
Since you mention applying the scope throws the "in progress" error, that seems to be valid (the digest is still working). This code frag checks for an in-progress digest and sidesteps it if necessary.
if ( ! $scope.$$phase ) {
$scope.$apply ();
}
Note that "$$" vars in Angular were "private". You could still access them but you're not "supposed" to. This does however have the benefit of not appearing as randomly vague as a zero timeout.
Alternatively, as you've already seen, a 0 timeout can force an update. That's a hack, but it does work and I've seen it used a few times.
Lastly, one of the lifecycle events may be a better choice for these traces, such that you know you're tracing information in the natural flow of the component, where digests are predictable.
Regarding that lifecycle hook, if you're using 1.5 (the last paragraph seems to fit right here and is probably what you want):
New: $onChanges
This new hook is similar to ng2’s ngOnChanges. It is called whenever one way bindings are updated, with a hash containing the changes objects.
Prior to this hook you sometimes had to use a $watch in order to do some work whenever a value you’re bound to changes. Using this hook makes things clearer and removes the need to introduce a watch and a dependency on $scope.

Is there a way to wrap jQuery's isArrayLike?

I'm trying to handle a conflict between XenForo and an essential plugin. Neither is code I can directly modify. jQuery 1.11.3 is the version in use, and that can't be upgraded either.
I can wrap the calling function with a try/catch and avoid the conflict, but the real fix would involve wrapping jQuery's isArrayLike with a try/catch.
isArrayLike is in the (current version) jQuery source at line 464 in https://github.com/jquery/jquery/blob/master/src/core.js#L464
What I need to catch seems to be exactly what was being debated in https://forum.jquery.com/topic/jquery-isarraylike-for-consistency with regard to isArrayLike throwing errors when passed invalid types.
So, since jQuery's isArrayLike isn't exposed, is there any way to either wrap it, or worst case, get in there and replace it?
Edit: Note, all the comments so far are trying to debug the error itself. That is not my question. I am asking if there is any way to access isArrayLike, so it can be wrapped with another function.
I would suggest replacing the jQuery each and map methods, which seem to be the only methods calling on isArrayLike, except for makeArray. But the fix I am suggesting is not necessary for the latter method.
Add this:
(function ($, origEach, origMap) {
$.each = function (elems, callback, arg) {
return origEach.call(this, Object(elems), callback, arg);
};
$.map = function (elems, callback, arg) {
return origMap.call(this, Object(elems), callback, arg);
};
})(jQuery, jQuery.each, jQuery.map);
It alters the first argument that is passed to map or each: it gets wrapped in an Object call, which changes nothing when it is an array, but turns the argument in an object if it is not an object (for some odd reason).
Patching jQuery
If you can use a patched version of jQuery, just store a copy of jQuery, and modify this line in the function isArrayLike:
var length = !!obj && "length" in obj && obj.length,
to:
var length = !!obj && "length" in Object(obj) && obj.length,
It should not be a problem for XenForo, as long as you explicitly add the patched jQuery via another script tag, after having included XenForo. The latest jQuery object overwrites the previous one, so XenForo will then also use the patched version, even though it included the non-patched jQuery library itself.
The downside of patching is that you cannot upgrade jQuery (implicitly with a XenForoupgrade) unless you patch it every time you upgrade (until the version where it is no longer necessary).
That is a problem you won't have with the first solution.

How do I execute a function only once in CoffeeScript

I want to make a CoffeeScript function that even if it is invoked multiple times, has its effects only run once.
Is one of these, or another way a good way to make a once-invokable function ? Is the extra do an issue or actually better ?
once_maker_a = (f)->
done=false
->
f.call() unless done
done=true
once_maker_b = (f)->
do(done=false)->
->
f.call() unless done
done=true
oa = once_maker_a(-> console.log 'yay A')
ob = once_maker_b(-> console.log 'yay B')
oa()
yay A #runs the function passed to the once_maker
undefined #return value of console.log
oa()
undefined #look, does not reprint 'yay A'
ob()
yay B
undefined
ob()
undefined
I know about http://api.jquery.com/one/ and http://underscorejs.org/#once but in this case using those libraries is not an option.
Is one of these a good way to make a once-invokable function?
As #UncleLaz stated in the comments, you're ignoring any arguments to the function. Also you don't memoize the return value of the function, and always just return true. If you're really only caring about side effects, then that might not be a problem.
Is the extra do an issue or actually better?
In your case it's an issue. Check out the compiled javascript. Even if you corrected the indentation, it's not better since it's just unnecessarily introducing another scope.
A better, minimalistic way might be
once_maker = (f) ->
->
f?.apply this, arguments
f = null
(still not caring about the return value)

Categories