The FDMSLib for the Flex-AJAX bridge has a load function that renders a flash object to the page where ever it is called. This causes problems when using Ext.JS as the inserted object can either be discarded by another render function or cause conflicts during the rendering of the page, so I'm trying to rewrite the load function so that its Ext.JS (and probably other JS framework) friendly.
This is the original function.
FDMSLibrary.load = function(path, callback)
{
var result = "<object id='_fesLib' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' \
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,5,0,0' \
height='1' width='1'> \
<param name='flashvars' value='bridgeName=flash'/> \
<param name='AllowScriptAccess' value='always'/> \
<param name='src' value='"+ (path != undefined ? path : "") + "'/> \
<param name='wmode' value='transparent' /> \
<embed name='_fesLib' pluginspage='http://www.macromedia.com/go/getflashplayer' allowScriptAccess='always'\
src='" + (path != undefined ? path : "") + "' height='1' width='1' flashvars='bridgeName=flash'/> \
</object>";
document.write(result);
// todo:need a callback and variable here so you can keep track that both the fabridge and the fdmsbridge are available
FDMSLibrary.addInitializationCallback("flash", callback);
// register for call back when the FABridge has completed initialization
FABridge.addInitializationCallback("flash", FABridge_ready);
}
I've replaced this with the following :
loadFDMSBridge: function(path,callback) {
var FABridgeSWF = new Ext.FlashComponent({
id : '_fesLib',
height: '1',
width: '1',
flashVars : {
bridgeName:'flash'
},
wmode : 'transparent',
flashVersion : '8.5.0.0',
renderTo : Ext.getBody(),
url : path,
listeners : {
'render' : {
fn : this.initBridge,
scope : this
}
}
});
},
initBridge : function () {
FDMSLibrary.addInitializationCallback("flash", this.initPolling);
FABridge.addInitializationCallback("flash", FABridge_ready);
}
The flash object is rendered to the page, it uses the data attribute in object rather than a src param, but I assume this is correct for rendering flash objects.
The problem I'm having with this is that when I call the FDMSLibrary.addInitializationCallback function it would appear that the bridge is not ready.
The javascript is told by the swf file that it is ready to be used. I'm pretty certain that its no a problem with the swf as I have an simple test page up and running using the same swf and javascript libraries, but without any interference from any other javascript, content and so on. It kind of feels like the swf isn't being loaded and "executed" and Firebug would seem to back this up, but I don't really know why or what I've missed.
If you would like to see the full javascript libraries and the actionscript source for the FABridge you can find them here in Adobe's subversion repo : fds ajax bridge repo path
Thanks in advance for your help
Not sure, but you might try the afterrender event instead of render. As it happens later in the rendering cycle (well, after it :) it might help -- I know that there have been issues in the past with render firing before certain pieces of a component were actually done rendering (which is why the afterrender event was later added).
Another thing if that doesn't work might be to try deferring your initBridge functionality slightly. This is a bit of a stretch, but I've seen the case before where there is either a timing issue with the JS execution, or possibly some blocking that causes this type of symptom. Deferring your code will both delay its execution and execute it on a new thread, which may solve the problem. Not the best solution, but I've worked around several browser quirks this way. E.g.:
initBridge : function () {
(function(){
FDMSLibrary.addInitializationCallback("flash", this.initPolling);
FABridge.addInitializationCallback("flash", FABridge_ready);
}).defer(500, this);
}
At least this might confirm whether or not it's a timing/blocking issue. You can adjust the 500 (ms) up or down to see if you hit any difference in behavior.
#bmoeskau : thanks for the helpful suggestions.
Looking at my code again with your suggestions I realised a couple of things.
First was that I was making changes in the wrong files. I'd made a standalone version and had accidentally been editing those files and not the ext.js application.
Secondly, with all the changes,testing and general messing about I had managed to overload some of my functions, which meant that the wrong functions were being called. No matter how much I changed the function I was looking at it never got called.
The above code actually works as stated.
Just to tidy this up :
I had continuing issues courtesy of a Firefox bug and swfobjects (the underlying library for Ext.Flashcomponent). I ended up having to push the original html from the FDMSLib load() function into the DOM rather than use the Ext object.
// spec up the flash object for FABridge
var foSpec = {
tag : 'object',
id : '_fesLib',
classid : 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000',
codebase: 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,5,0,0',
height :'1',
width : '1',
cn : [
{tag:'param', name:'flashvars', value:'bridgeName=flash'},
{tag:'param', name:'AllowScriptAccess', value:'always'},
{tag:'param', name:'src', value:path},
{tag:'param', name:'wmode', value:'transparent'},
{tag:'embed', name:"_fesLib", pluginspage:'http://www.macromedia.com/go/getflashplayer', allowScriptAccess:'always', src:path, height:'1', width:'1', flashvars:'bridgeName=flash'}
]
};
// Add it to the end of the body
Ext.getBody().createChild(foSpec);
I vaguely recall a post somewhere that says that FABridge uses the "embed" tag and as swfobject doesn't insert an "embed" tag this may also have been causing issues.
To add to this, the flashParams property of Ext.Flashcomponent makes reference to this adobe technote on required params. This states that an object only inclusion of a flash object must include the param "movie". swfobject forcibly removes any param with the name "movie".
Not much fun to be had anywhere with this!
Related
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);
How to check if a given object is a d3 selection?
The following code prints true in Chrome and Firefox, but false in Internet Explorer:
console.log(d3.select(document.body) instanceof d3.selection)
Update 2017-01-17
With the release of D3 v4 this problem has vanished (changelog):
Selections no longer subclass Array using prototype chain injection; they are now plain objects, improving performance.
And the API docs explicitly state:
# d3.selection() <>
[…] This function can also be used to test for selections (instanceof d3.selection)
Using the new version, the following code will actually evaluate to true in all browsers:
d3.select() instanceof d3.selection // true in Chrome, FF, IE
For all those still on v3 the original answer below has an analysis and a workaround for the problem.
Problem
Due to the inner workings of D3 every browser which supports Object.prototype.__proto__ will print true, whereas browsers lacking support for __proto__ will print false. Checking the compatibility list it's obvious, that IE<11 will evaluate the expression to false. For this reason, you won't be able to use instanceof d3.selection to check for a D3 selection in IE<11. This is a known issue with D3, but it was closed and will not get fixed.
Analysis
From D3's github repository:
selection/selection.js
Looking at the definition of d3.select() which is the entry point of your call:
d3.select = function(node) {
// ... removed for brevity
return d3_selection([group]);
};
This will eventually return the result of the call to d3_selection(), which in turn will subclass d3_selectionPrototype = d3.selection.prototype.
function d3_selection(groups) {
d3_subclass(groups, d3_selectionPrototype);
return groups;
}
core/subclass.js
Finally, the implementation of d3_subclass() provides the answer to the problem:
var d3_subclass = {}.__proto__?
// Until ECMAScript supports array subclassing, prototype injection works well.
function(object, prototype) {
object.__proto__ = prototype;
}:
// And if your browser doesn't support __proto__, we'll use direct extension.
function(object, prototype) {
for (var property in prototype) object[property] = prototype[property];
};
It checks, if the browser supports Object.prototype.__proto__ by checking for the existence of the __proto__ accessor property on an empty object {}. If the browser supports it, D3 will directly assign the prototype, thus making it an instance of d3.selection. Otherwise, all properties of the prototype will be copied over to the object to be returned without explicitely setting the prototype. In this case your expression will evaluate to false.
Workaround
Because d3.selection is provided as a means to extend the selection's functionality you could implement a workaround by adding a new property to d3.selection which will, as was explained above, be made accessible by any selection, wether by prototyping or by copying properties.
// Include this at the start of your script to include the
// property in any selection created afterwards.
d3.selection.prototype.isD3Selection = true;
console.log(d3.select(document.body).isD3Selection); // true in any browser
Some extremely simple problem for those experienced with backbone,
but still, an answer here would very helpeful. Not looking for an functional answer, but more about what's really happening on this specific example.
With the following code (some simple add/remove from favorite)
render: function() {
$(this.el).html(this.model.get('name'));
$(this.el).append("<span class='unfav'>remove</span>");
$(this.el).append("<span class='fav'>add</span>");
if( this.model.get("selected") == true ){
$(this.el).addClass("selected");
} // Should we really need to have an 'else' conditions here that removes the clas :( ? sound weird to me.
return this;
}
Full code here http://jsfiddle.net/eHAfY/3/
(thanks to #cymen for the codebase)
After adding an element,
Don't get why the item gets changed when I click on 'Add', and does not when I click on remove : if there is a condition that have effect when true, why it is the class' still here when false ?
Your render method wipes its inner HTML with $(this.el).html(...); which is what render methods are usually expected to do.
With $(this.el).addClass("selected"); you modify the external container only when model.selected is true, and your view has no way to know that it should remove this class when the condition is false.
Since extensions can not access unsafeWindow, like Firefox can, to hook into DOM scripts am I looking for other ideas so I come to SO for help!
How about using some code to inject into DOM and sending the intercepted response to a background page, which then does some initial processing before calling a content script for final processing. When done, it answers to the background with a modified response, or the original (it depends), and the background page sends the response back to DOM which handles it to the DOM script response function.
There is just one problem with this, a background page cant communicate with the DOM.
I did a small test with injecting some code, where I output something to the console and an alert. The result wasnt good, as the alert fired but the console was empty - not even an error, which makes me wonder - what console received the output ?
function injectCode(fn){ // Executing an anonymous script
var script = document.createElement('script');
script.type = 'application/javascript';
script.textContent = '(' + fn + ')();';
document.documentElement.appendChild(script); // run the script
document.documentElement.removeChild(script); // clean up
}
var code = function(){
console.log('dom',window);
alert('code injected');
}
injectCode(code);
I also tried addEventListener, with DOMAttrModified DOMSubtreeModified DOMNodeInserted, on DOM elements that change when the DOM ajax response is fully parsed but all failed to fire.
Am I trying to do the impossible, by any means ?
Before continuing, make sure that you know the differences between the script contexts in an extension.
To inject a script from the background page, you have to execute a Content script, which on his turn injects the script as mentioned in your question / here.
Examples (using chrome.tabs.executeScript):
// null = current active tab
// Simple code, background:
chrome.tabs.executeScript(null, {
code: [
'var s = document.createElement("script");',
's.textContent = "console.log(window);";',
'(document.head||document.documentElement).appendChild(s);',
's.parentNode.removeChild(s);'
].join('\n')
});
I can imagine that this method is not doable for a big chuck of code. For a set of pre-defined scripts, you can then use two scripts: the code itself, and a helper script:
// config.js
var fn_code = function() {
console.log(window); ....
};
// helper.js
var s = document.createElement('script');
s.textContent = '(' + fn_code + ')();';
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
// Background:
chrome.tabs.executeScript(null, {file: 'config.js'}, function() {
chrome.tabs.executeScript(null, {file: 'helper.js'});
});
Note: I did not directly link to "config.js", because that complicates the use when using manifest version 2, see "web_accessible_resources".
The previous method only shows how to execute code in one direction (background -> page). If there's a need to activate a background's function from the injected script, you have to define and listen to a custom event handler. See this answer + demo.
Because the code is injected, thus runs in the scope of the page, you have to check the console at the page.
When chrome.tabs.executeScript fails to execute the Content script (eg. because the extension does not have the permission to access a certain page), an error is logged at the console in the background page. This console can be accessed by following these steps.
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.