I'm building a proof of concept test with CasperJS and I'm trying to wrap my head around when things are available and when they are not.
I'm a bit fuzzy on why evaluate() is required and why one cannot just get a page and go to town with document.querySelectorAll(), but that ends up giving me a nodeList with a length of zero... even after casper.echoing out the HTML and SEEING plenty of matching selectors.
Right now, I'm trying this:
var tile;
var pid;
casper.waitForSelector('.m-product-tile', function() {
tile = this.evaluate(function() {
return __utils__.findOne('.m-product-tile');
});
pid = this.evaluate(function()
return __utils__.findOne('.m-product-tile').getAttribute('data-pid');
});
//do cools stuff like click() on tile and make sure the URL has the pid in it
});
But this feels wrong as I am not sure __utils__.findOne() will always return the same element.
I'd love to be able to do this instead:
pid = tile.getAttribute('data-pid');
But when I attempt that, I get an error:
uncaughtError: TypeError: 'undefined' is not a function (evaluating 'tile.getAttribute('data-pid')')
Did tile somehow cease to be a DOMNode with a getAttributes method? Using typeof tile, it still thinks it's an object. I echoed out the DOMNode and it has a getAttributes key.
The code in the first example DOES work with getAttribute... so why does tile morph into something that no longer has a getAttributes method?
http://casperjs.readthedocs.org/en/latest/modules/casper.html#evaluate provides a pretty good answer... but still, I'd like to know why, if what the page is passing back, is a DOMNode, why can't I treat it like one?
Related
I would like to be able to use the dc.js select menu (dc.selectMenu) in such a way that when I click on an element it gets the value of said element and that becomes the value of the select, once selected it should refresh the data as it normally would if you had just selected in the first place.
The problem I'm having is that I can set the value, but dc.redrawAll() seems to do nothing for me so I think I must be filtering wrongly, but I can't find much information online regarding how to do it other than simply using the filter method (not onclick).
I have tried to set the destination to whatever data-destination is which appears to be working, the value of the select does update when I check with console.log to check the value of the select menu, I then use the dc.redrawAll() function expecting it would filter based on the select option but it does nothing (not even an error in the console)
My function so far is looking like:
function select_destination(ndx) {
var destination_dim = ndx.dimension(dc.pluck('destination'));
var destination_group = destination_dim.group();
var destination = null;
document.addEventListener('click', function(e) {
if (!e.target.matches('.open-popup-link')) return;
e.preventDefault();
var destination = e.target.getAttribute('data-destination').toString();
document.getElementById('select-destination').value = destination;
dc.redrawAll();
});
dc.selectMenu('#select-destination')
.dimension(destination_dim)
.group(destination_group)
.filter(destination);
}
I would expect the graphs to update based on the select option but nothing happens, and I get no error message to go off either.
I suspect I'm using dc.redrawAll() wrongly as if I go to the console and type dc.redrawAll(); I get undefined but I'm really at a loss now and the documentation isn't really helping me at this point so I don't know what else to do.
they are bits of your code that I don't quite understand, for instance why do you have have filter(destination /*=null */)
anyway, So you want to filter the select menu? you can call directly the replaceFilter function with the value, as done in the source code:
menu.replaceFilter(destination);
dc.events.trigger(function () {
menu.redrawGroup();
});
See the source code for the full example of how it's done
https://dc-js.github.io/dc.js/docs/html/select-menu.js.html#sunlight-1-line-129
as for why it doesn't work, I have had some surprising results mixing d3 with pure dom js. Try to rewrite your even handler in d3, eg
d3.select('#select-destination').property('value', destination);
it's possibly that changing the value on the dom directly isn't triggering the change event.
My experience with d3 is that it works better to change the underlying data (call directly filter functions or whatever you want to do) and let dc redraw the needed rather than manipulating the dom directly
I have a group of objects (actually 3D text on an arc) that I want to remove from the scene on a specific click. Does .remove not work on groups? Here's basically what I have:
$(".inscript").on("mousedown", function(event){
var x = scene.getObjectByName("inscriptArc");
scene.remove(x);
});
This answer seems to suggest you can (remove a group from a scene using .remove), but it isn't working for me.
THREE.Scene.getObjectByName returns the first instance of a child with that name. If you have multiples you won't catch them by calling it once.
To remove all instances I would use the THREE.Object.traverse(fn(child){}) function ie:
var children_to_remove = [];
scene.traverse(function(child){
if(child.name == "inscriptArc"){
children_to_remove.push(child);
}
});
children_to_remove.forEach(function(child){
scene.remove(child);
});
you might be able to just do
scene.traverse(function(child){
if(child.name == "inscriptArc"){
scene.remove(child);
}
});
but I think there are some cases where this could cause an error if you are loading/removing anything from the scene asyncronously - because removing a child might throw an error when that child's children get traversed. Not sure, so I would try the simple one and swap with the more complicated one if it causes issues.
Yes, you definitely can remove a group of objects. You might have named children of the object instead of the actual group. Function getObjectByName did not always work as promised in older three.js releases so maybe try this.
scene.children.forEach(child => child.name == "inscriptArc" ? scene.remove(child) : null)
I'm using leaflet-ajax to load geoJSON on demand. I want to find the maximum theProperty value so I can use that to scale the feature's fill colors before I add them to the map.
Here's my general approach:
function maxBinPropertyValue(theProperty) {
var theGeoJson = null;
var maxPropertyValue = 0;
var propertyValue = null;
var theGeoJson = new L.GeoJSON.AJAX(binsFileName());
theGeoJson.on('data:loaded', function() {
console.log('The data is loaded');
theGeoJson.eachLayer(function(layer) {
console.log('Looping through the layers');
propertyValue = feature.properties[theProperty];
if (propertyValue > maxPropertyValue) {
maxPropertyValue = propertyValue;
console.log('Max so far: ' + maxPropertyValue);
};
});
});
theGeoJson = null;
console.log('The final maximum value: ' + maxPropertyValue);
return maxPropertyValue;
};
I'm trying to wait for the data:loaded event, then loop through all the features to find the maximum value of theProperty, which is returned to the calling routine.
Except it doesn't work. The first console.log says 'The data is loaded'. The second and third console.logs are never reached, and the fourth and final one reports a value of 0 for maxPropertyValue.
How can I examine all the features in a featureset before styling them, in a way guaranteed to not have asynchronous problems?
PS: I'm pretty sure I can't use onEachFeature: instead of the above approach, because I need to examine every feature's property to determine the maximum value in the set before I can style any of the features.
As for your issue about inspecting your data and retrieving the maximum value, you are indeed facing the classic asynchronous concept of JavaScript.
See How do I return the response from an asynchronous call?
Asynchronism is a problem if not dealt with properly, but an advantage if correctly handled.
To put the concept shortly, you do not manage asynchronism in a "standard" sequential way, but you should rather consider parts of code (callbacks) that are executed at a later time based on events.
Whenever you provide a function as an argument, it is certainly a callback that will be executed at a later time, but very probably much later than the next instructions.
So in your case, your 2nd and 3rd console.log are within a callback, and will be executed once your data is loaded, which will happen much later than your 4th console.log.
As for your next step (styling and adding to map), you actually do not need to perform an extra AJAX call, since you already have all data available in theGeoJson variable. You simply need to refactor / restyle it properly.
It is a good approach to break your problem in small steps indeed.
Good luck!
PS: that being said, ES7 provides async and await functionalities that will emulate a sequential execution for asynchronous functions. But to be able to use those, you need latest browser versions or transpilation, which is probably more work to learn and configure as of today for a beginner than understanding how to work with async JS.
I also had this problem and had to wrap my head around this, so giving an explicit example for solution here;
// make a request with your "url"
var geojsonLayer = new L.GeoJSON.AJAX("url");
// define your functions to interact with data
function thingToDoBeforeLoadingStarts () {
// do stuff
}
function thingToDoForEachFileDownloaded () {
// do stuff
}
function thingToDoAfterAllDownloadEnds () {
// do stuff
}
// attach listeners
geojsonlayer.on("data:loading",thingToDoBeforeLoadingStarts);
geojsonLayer.on("data:progress",thingToDoForEachFileDownloaded)
geojsonLayer.on("data:loaded",thingToDoAfterAllDownloadEnds);
I admittedly know little about the inner workings of javascript, but need to make a library and would like to learn (hence asking here). I understand using the closure and exporting to window to not pollute the global namespace, but beyond that it confuses me a bit.
(function() {
var Drop = window.Drop = function() {
var files = [];
var add = function(word) {
files.push(word);
return files;
}
return {
files: files,
add: add
}
}
})()
// All of these seem to be the same?
var a = Drop();
var b = new Drop();
var c = new Drop;
// Each has their own state which is what I want.
a.add("file1");
b.add("file2");
c.add("file3");
Why are all three ways of "initializing" Drop the same?
What exactly gives them the ability to have their own state?
Is there an alternative to the return syntax to export those functions on Drop?
Is there just a flat out better best practice way of creating a self contained library like this?
I have searched around the net, but have found very little consistency on this subject.
The first way (Drop()) just calls the function as normal, so this is the global object (window in browser environments). It does its stuff and then returns an object, as you'd expect.
The second way (new Drop()) creates a new Drop object and executes the constructor with this set to that object. You do not, however, use this anywhere and return an object created from an object literal, so the Drop object is discarded and the object literal returned instead.
The third way (new Drop) is semantically the same as the second; it is only a syntactic difference.
They all have their own state because each time you call Drop, it has its own set of local variables distinct from the local variables of any other call to Drop.
You could transform your code to use the normal new syntax and prototypes. This has a few advantages: namely, you only create the add function once rather than one for each Drop call. Your modified code might look like this:
function Drop() {
this.files = [];
}
Drop.prototype.add = function(word) {
this.files.push(word);
return this.files;
};
By doing this, though, you lose being able to call it without new. There is, however, a workaround: You can add this as the first line inside function Drop:
if(!(this instanceof Drop)) {
return new Drop();
}
Since when you call it with new, this will be a Drop, and when you call it without new, this will be something other than a Drop, you can see if this is a Drop, and if it is, continue initializing; otherwise, reinvoke it with new.
There is also another semantic difference. Consider the following code:
var drop = new Drop();
var adder = drop.add;
adder(someFile);
Your code will work here. The prototype-based code will not, since this will be the global object, not drop. This, too, has a workaround: somewhere in your constructor, you can do this:
this.add = this.add.bind(this);
Of course, if your library's consumers are not going to pull the function out of the object, you won't need to do this. Furthermore, you might need to shim Function.prototype.bind for browsers that don't have it.
No. It's all a matter of taste.
Why are all three ways of "initializing" Drop the same?
// All of these seem to be the same?
var a = Drop();
var b = new Drop();
var c = new Drop;
When you use new in JavaScript to invoke a function, the value of this inside the function becomes the new object.
But the reason they're the same in your case is that you're not using this at all. You're making a separate object using object literal syntax, and returning it instead, so the new has no impact.
What exactly gives them the ability to have their own state?
Because each function invocation makes a new object, each object is entirely different for each invocation.
The functions assigned to the object are recreated in each Drop invocation, and therefore create a closure over the enclosing variable scope. As such, the files array of each invocation is continuously accessible to the functions made in each respective invocation.
Is there an alternative to the return syntax to export those functions on Drop?
Yes. Assign the functions and array to this, and remove the return statement. But that will require the use of new. Alternatively, put the functions on the .prototype object of Drop, and they'll be shared among all instances made using new, but keep the array assigned to this in the constructor so that it's not shared.
For the prototyped functions to reference the array, they would use this.files.
Is there just a flat out better best practice way of creating a self contained library like this?
JavaScript is very flexible. There are many ways to approach a single problem, each with its own advantages/disadvantages. Generally it'll boil down to taking advantage of closures, of prototypal inheritance, or some combination of both.
Here's a full prototypal inheritance version. Also, the outer (function() {})() isn't being used, so I'm going to add a variable to take advantage of it.
(function() {
var totalObjects = 0; // visible only to functions created in this scope
var Drop = window.Drop = function() {
this.files = [];
this.serialNumber = totalObjects++;
}
Drop.prototype.add = function(word) {
this.files.push(word);
return this.files;
};
})();
I am trying to remove an element on AJAX success which was loaded and attached to the document during a previous AJAX call.
My code looks something like this:
$("#jobs-table-body").on("click", ".one-rc-button", function() {
var ctx = $.parseJSON($(this).siblings(".context").html());
$("#one-rc-candidate-id").val(ctx.candidateId);
$("#one-rc-job-id").val(ctx.jobId);
var loader = $("#wrapper").loader();
$.post($("#one-rc-form").attr("action"), $("#one-rc-form").serialize(), function(result) {
loader.remove();
if(result.success) {
// This works and returns 1
alert($("#candidate-row-" + result.rejectedCandidateId).length);
// This doesn't seem to be doing anything
$("#candidate-row-" + result.rejectedCandidateId).remove();
} else {
//$("#one-jc-messages").html(result.error);
}
});
});
The elements .one-rc-button and #candidate-row-<candidateId> were loaded by a previous AJAX call and they are attached to the document as I can very well see them on my page.
Now, on click of the previously generated .one-rc-button, I trigger a second AJAX call (which works fine) and on result.success, I want to delete the #candidate-row-<candidateId> (which is within the previously generated parent element).
The alert works and returns 1. So I know for sure that the selector is fine and it is matching one unique element.
What I don't understand is why it is unable to remove the element from the page.
Observations
I use Firefox 10.0.2 where this problem is reproducible.
On IE 8, it works (element gets removed)
On debugging the script on Firebug, I can verify that I have got a handle to the right eleemnt.
Try using FireBug to set a breakpoint on that line so you can see exactly what it's getting from that selector. Ideally break up the statement first, like this:
var unwantedDiv = $("#candidate-row-" + result.rejectedCandidateId);
unwantedDiv.remove(); // <-- Set a breakpoint on this line
You can then look at the unwantedDiv variable in the watch pane on the right of the firebug debugger and see what it is, what methods it has/has not got etc. I would assume that you are not getting back exactly what you think you are, possibly because of how you attached the div after the previous AJAX call. More information about JavaScript debugging with FireBug here.
Another option is to turn on strict warnings in the firebug console and see if you get any 'undefined method' errors, which don't stop the show on FireFox, but just bounce you out of that function. Do you get an error in IE?
Solved it by a really ugly workaround. I am still not sure what causes this behaviour.
if(result.success) {
var removeThis = $("#candidate-row-" + result.rejectedCandidateId);
removeThis.remove();
removeThis = $("#candidate-row-" + result.rejectedCandidateId);
if(removeThis.length != 0) {
removeThis.remove();
}
}
Now it works on both Firefox and IE.