I am working on a website globalization project, which involves (us the vendor) asking our clients to insert a script tag on their home/origin site. The script tag is necessary for helping our clients go global, and part of the solution embodies a UI that gets triggered based on certain end user criteria.
The UI is built through the help of jQuery, which we really can't expect our clients to insert on their pages, not to mention version mismatches will be hard to resolve. Therefore, our third party library downloads its own jQuery version, albeit namespaced differently to avoid any conflicts.
However, such a mechanism requires us to rename all instances of jQuery to something that will help us avoid name clashes with another jQuery instance (if present), and makes the our mangled jQuery (MY_Query in examples below) very hard to manage, let alone upgrade.
For example
jQuery = window.jQuery = window.$ = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
.
.
.
jQuery.fn = jQuery.prototype = ...
becomes
MY_JQuery = window.MY_JQuery = window.MY_Q = function( selector, context ) {
// The MY_JQuery object is actually just the init constructor 'enhanced'
return new MY_JQuery.fn.init( selector, context );
},
.
.
.
MP_JQuery.fn = MP_JQuery.prototype = ...
In an ideal world, both us and the client would have a single version of jQuery on the site, and we both would use it to our advantage. But that would mean an upgrade of jQuery would require heavy testing on both sides (while the mangled jQuery version is contained) and that any plugin wanted would require the client to add appropriate script tags to their site, spurring a political debate between the two parties on what versions win.
So, can I manage our jQuery version (with plugins) on a client site without having to rename all instances of jQuery with something like MY_Query with the constraints mentioned above?
Why not check to see if they have jQuery already included on the page and if not dynamically load it? If you know the base level jQuery needed you can check for that like this:
if( !jQuery || !jQuery.fn.jquery === "1.4.4"){
var url = "http://code.jquery.com/jquery-1.4.4.js";
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.src = url;
document.body.appendChild(script);
}
You'd probably want to improve the version detection to make sure that it doesn't have a version after 1.4.4, but I'm sure you could write the code for that yourself ;-)
==== Edit based on feedback
So you need to maintain multiple versions of jquery on the page. Have you tried something like this:
var original_jquery = $().noConflict();
original_jquery.getScript("http://code.jquery.com/jquery-1.4.4.js");
var new_jquery = $().noConflict();
window.$ = original_jquery;
Then use the new_jquery as your version of jquery? I haven't tested this to see if it would work, but you might have some luck with it.
==== Final edit
As you mentioned, my javascript above wasn't exactly correct, so I tried out a few things in the console. And yes, you don't need to save the old version of jQuery because jQuery does that in the noConflict method. So just call getScript, then noConflict but saving to a new variable:
>> $.fn.jquery
"1.4.2"
>> $.getScript("http://code.jquery.com/jquery-1.4.4.js");
undefined
>> $.fn.jquery
"1.4.4"
>> var new_jquery = $.noConflict();
undefined
>> new_jquery.fn.jquery
"1.4.4"
>> $.fn.jquery
"1.4.2"
Have you tried to use JQuery.noConflict(). This sounds like it could help you.
Related
I am learning about writing custom JavaScript for my Odoo 10 addons.
I've written the following piece of code:
odoo.define('ioio.io', function(require) {
'use strict'
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
})
The code is well loaded and I can see my testing string in the console.
However when this code is being loaded before the target div, so e empty/not yet filled and thus its content is not removed.
Doing it manually from the console works.
My question is what is the right way to do that? And how to know exactly when the code gets executed?
You can
put your html code before the script tag in your file
use jQuery $(document).ready(...);
Place your script at the bottom of the <body> tag to make sure the DOM renders before trying to manipulate it.
This is an Odoo specific question, so you should use the Odoo standard way, which is via its base JS class. That class contains a ready() method which does exactly what you need.
In your case, to use that function, you need to require the class first. Then you can use ready().
Updating your code, it should look like this:
odoo.define('ioio.io', function(require) {
'use strict'
// require base class
var base = require('web_editor.base');
//use its ready method
base.ready().done(function () {
// put all the code you want to get loaded
// once the DOM is loaded within this block
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
});
})
While your accepted answer leads to the same outcome, you might want to update it to this one since this is the Odoo way. It's generally advised to work within the Odoo framework as much as possible and customise only if really needed. (Though it can be tough to learn what features Odoo already provides because of its poor documentation.)
So I'm attempting work within the constraints of a request and have made some progress bet am still getting locked up. Here's the scenario:
Currently, I'm working with a website that depends on jQuery v1.4.2. Since the only access I'm given on the backend is the ability to inject content into a CMS driven page, I had been sideloading jQuery 1.11 to support for some much needed functionality.
Flash forward to yesterday and I was made aware that the client's dev team has now bundled v1.4 and v1.11 and has noConflicted 1.11 into $$.
(function() {
$$ = $.noConflict( true ); // Move jQuery 1.11.0 into $$ and restore $ to jQuery 1.4.2
})();
// Use the following self-invoking anonymous function
// when you need to run code that depends on $ = jQuery 1.11
// Otherwise, you can access jquery 1.11 with $$
// (function( $ ){
// inside here $ is jQuery 1.11
// and jQuery 1.4.2 is out of scope
// })( $$ );
Since my scripts (and the plugin I am attempting to load into the page) are injected in the middle of the page and the jQuery v1.4 and v1.11 are at the bottom of the page, I am loading it this way:
(function checkForJquery() {
if ('$$' in window) {
(function($) {
console.log('success');
$.getScript("myplugin.jquery.js", function() {
$( ".devices").myplugin({
// devices - Array | productID
prop1: ["prod3960155", "prod3640151", "prod3640152", "prod5530141"],
prop2: "attribute",
prop3: "attribute"
});
});
})($$);
} else {
console.log('not yet');
window.setTimeout(checkForJquery, 1000);
}
})();
So the issue I'm having is that if I use:
(function( $ ){})( $$ );
I get nothing - the plugin never initializes. However, if I use:
(function( $ ){})( jQuery );
it does work, but then I don't have access to the latest API. Any ideas why one would work and not the other?
For clarity, I'm using the jQuery Plugin Boilerplate and it starts like this:
;( function( $, window, document, undefined ) { } )( jQuery, window, document );
Any insights would be much appreciated!
Thanks,
Joe
The reason your script won't work is not your call of the plugin, it is the creation.
To explain, the code below only uses the second instance $$ to search for all elements with the class .devices and starts your plugin myPlugin. Nothing else.
(function($) {
$(".devices").myplugin();
})($$);
But you have registered your plugin only to the old version. This is the reason why it workes, when you use jQuery instead of $$ on the code above.
To use the $$ jQuery instance in your plugin, you have to set it on creation too. But there you has passed in jQuery. And because you used noConflict, the name jQuery belongs to the old version of jQuery.
To register your plugin to the right instance, you have to change it to $$ on the creation too.
;(function($, window, document, undefined){})($$, window, document);
To make your plugin working on other installations, you can add jQuery as a fallback. This would help if you use the plugin on other sites too and don't want to change it every time.
;(function($, window, document, undefined){})($$ || jQuery, window, document);
Thats it.
I am developing a Single Page Application in Clojurescript, and I want to use TinyMCE as a WYSIWYG editor for certain fields. For space efficiency, I want to eventually minify the project using the google clojure compiler in advanced mode. Since the tinymce dev javascript files seems to be unsuitable for use as an extern file, I'm forced to write my own.
There is one particular function call that I can't get to work. In clojurescript, I call:
(.setContent (.get js/tinymce id) cont)
which I'd imagine would compile to something like:
tinymce.get(id).setContent(cont);
I have tried many different function definitions in my externs, yet I keep getting an error:
TypeError: tinymce.get(...).Tl is not a function
Which tells me setContent gets obscured away by the compiler. My current extern file looks like this:
//all seems to be well here...
var tinymce;
tinymce.get = function(name){};
tinymce.remove = function(node){};
tinymce.init = function(editor){};
tinymce.on = function(name, callback, prepend, extra){};
//tinymce setContent attempts
var tinymce.Editor;
tinymce.Editor.prototype.setContent = function(content){};
tinymce.Editor.setContent = function(content){};
tinymce.get(name).setContent = function(content){};
tinymce.get(name).prototype.setContent = function(content){};
var Editor;
Editor.prototype.setContent = function(content){};
Editor.setContent = function(content){};
Which currently is kind of a throw-everything-against-the-wall-and-see-what-sticks attempt. The object get(name) returns should be in the namespace tinymce.Editor.
Is there a proper way of writing an externs to catch these chained function calls? Or is there a way to rewrite the first code snippet so my externs properly preserve the function names? Thanks in advanced.
This line:
var tinymce.Editor;
is not syntactically correct in JS:
SyntaxError: missing ; before statement
Remove it and leave this line:
tinymce.Editor.prototype.setContent = function(content){};
This should work fine.
Also you may always fall back to accessing properties via string literals which will never be renamed:
(require '[goog.object :as gobj])
(gobj/get your-object "i-wont-be-renamed")
I found a function (via this person's github) that I might use in my script that mimics the functionality of an API object.
Here's the relevant code from the link:
unsafeWindow = (function() {
var e1 = document.createElement('p')
e1.setAttribute('onclick', 'return window;');
return e1.onclick();
})();
Where the poster says you can use the function in the format unsafeWindow.jQuery
Now, I want to be able to use $ instead of the jQuery keyword elsewhere in my code. I tried learning from this stack overflow question to simplify it and re-wrote the code like so:
(function($){
var e1 = document.createElement('p')
e1.setAttribute('onclick', 'return window;');
return e1.onclick();
})(jQuery);
But it didn't work. I guess I could just try something like $ = unsafeWindow.jQuery in order to map to the $, but I wanted to try to do it in the format seen above.
You would map $ to unsafeWindow.jQuery like so:
unsafeWindow = ( function () {
var dummyElem = document.createElement('p');
dummyElem.setAttribute ('onclick', 'return window;');
return dummyElem.onclick ();
} ) ();
var $ = unsafeWindow.jQuery;
// Now you can use the page's jQuery. EG:
$("body").append ('<p>Content added by unsafeWindow.jQuery</p>');
But keep in mind:
This is a Hack, and it will probably stop working around Chrome version 28.
It may still fail due to a race condition about when userscripts fire. To fix that, add // #run-at document-end to the userscript's metadata block.
Don't do things this way! It will only cause grief, side effects and maintenance headaches.
For userscripts: use this technique (best cross-browser) or this technique (relies on page's jQuery, but the example shows how to use GM_ functions too).
For full extensions or content scripts:, use this technique (use the manifest.json and keep everything properly sandboxed).
Suppose we need to embed a widget in third party page. This widget might use jquery for instance so widget carries a jquery library with itself.
Suppose third party page also uses jquery but a different version.
How to prevent clash between them when embedding widgets? jquery.noConflict is not an option because it's required to call this method for the first jquery library which is loaded in the page and this means that third party website should call it. The idea is that third party site should not amend or do anything aside putting tag with a src to the widget in order to use it.
Also this is not the problem with jquery in particular - google closure library (even compiled) might be taken as an example.
What solutions are exist to isolate different javascript libraries aside from obvious iframe?
Maybe loading javascript as string and then eval (by using Function('code to eval'), not the eval('code to eval')) it in anonymous function might do the trick?
Actually, I think jQuery.noConflict is precisely what you want to use. If I understand its implementation correctly, your code should look like this:
(function () {
var my$;
// your copy of the minified jQuery source
my$ = jQuery.noConflict(true);
// your widget code, which should use my$ instead of $
}());
The call to noConflict will restore the global jQuery and $ objects to their former values.
Function(...) makes an eval inside your function, it isn't any better.
Why not use the iframe they provide a default sandboxing for third party content.
And for friendly ones you can share text data, between them and your page, using parent.postMessage for modern browser or the window.name hack for the olders.
I built a library to solve this very problem. I am not sure if it will help you of course, because the code still has to be aware of the problem and use the library in the first place, so it will help only if you are able to change your code to use the library.
The library in question is called Packages JS and can be downloaded and used for free as it is Open Source under a Creative Commons license.
It basically works by packaging code inside functions. From those functions you export those objects you want to expose to other packages. In the consumer packages you import these objects into your local namespace. It doesn't matter if someone else or indeed even you yourself use the same name multiple times because you can resolve the ambiguity.
Here is an example:
(file example/greeting.js)
Package("example.greeting", function() {
// Create a function hello...
function hello() {
return "Hello world!";
};
// ...then export it for use by other packages
Export(hello);
// You need to supply a name for anonymous functions...
Export("goodbye", function() {
return "Goodbye cruel world!";
});
});
(file example/ambiguity.js)
Package("example.ambiguity", function() {
// functions hello and goodbye are also in example.greeting, making it ambiguous which
// one is intended when using the unqualified name.
function hello() {
return "Hello ambiguity!";
};
function goodbye() {
return "Goodbye ambiguity!";
};
// export for use by other packages
Export(hello);
Export(goodbye);
});
(file example/ambiguitytest.js)
Package("example.ambiguitytest", ["example.ambiguity", "example.greeting"], function(hello, log) {
// Which hello did we get? The one from example.ambiguity or from example.greeting?
log().info(hello());
// We will get the first one found, so the one from example.ambiguity in this case.
// Use fully qualified names to resolve any ambiguities.
var goodbye1 = Import("example.greeting.goodbye");
var goodbye2 = Import("example.ambiguity.goodbye");
log().info(goodbye1());
log().info(goodbye2());
});
example/ambiguitytest.js uses two libraries that both export a function goodbye, but it can explicitly import the correct ones and assign them to local aliases to disambiguate between them.
To use jQuery in this way would mean 'packaging' jQuery by wrapping it's code in a call to Package and Exporting the objects that it now exposes to the global scope. It means changing the library a bit which may not be what you want but alas there is no way around that that I can see without resorting to iframes.
I am planning on including 'packaged' versions of popular libraries along in the download and jQuery is definitely on the list, but at the moment I only have a packaged version of Sizzle, jQuery's selector engine.
Instead of looking for methods like no conflict, you can very well call full URL of the Google API on jQuery so that it can work in the application.
<script src="myjquery.min.js"></script>
<script>window.myjQuery = window.jQuery.noConflict();</script>
...
<script src='...'></script> //another widget using an old versioned jquery
<script>
(function($){
//...
//now you can access your own jquery here, without conflict
})(window.myjQuery);
delete window.myjQuery;
</script>
Most important points:
call jQuery.noConflict() method IMMEDIATELY AFTER your own jquery and related plugins tags
store the result jquery to a global variable, with a name that has little chance to conflict or confuse
load your widget using the old versioned jquery;
followed up is your logic codes. using a closure to obtain a private $ for convience. The private $ will not conflict with other jquerys.
You'd better not forget to delete the global temp var.