最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How can MyWebsite ship a javascript package jQuery or AngularJS, that websiteA and websiteB can use, without conflicts? - Stack

programmeradmin4浏览0评论

Say I have a website, MyWebsite, where you can build content for a call-to-action-box that should show on another website, WebsiteA, and on more websites that choose our solution without using an iframe.

How can that box be created using jQuery or AngularJS, without conflicts, without knowing what the customer has in their webpage, they just import our some_script.js set some settings and that's it. Most importantly, we can't mess up the customers side (obviously), nor should whatever tech the customer is using, mess up our superduper box.

I would love to see a working example. This is hard (I think), so please take more time to think before answering if you haven't solved such a problem. Appreciate the help guys!

Say I have a website, MyWebsite, where you can build content for a call-to-action-box that should show on another website, WebsiteA, and on more websites that choose our solution without using an iframe.

How can that box be created using jQuery or AngularJS, without conflicts, without knowing what the customer has in their webpage, they just import our some_script.js set some settings and that's it. Most importantly, we can't mess up the customers side (obviously), nor should whatever tech the customer is using, mess up our superduper box.

I would love to see a working example. This is hard (I think), so please take more time to think before answering if you haven't solved such a problem. Appreciate the help guys!

Share Improve this question edited Aug 25, 2014 at 2:58 Andrew Eisenberg 28.8k9 gold badges97 silver badges151 bronze badges asked Aug 15, 2014 at 19:01 omaoma 40.8k12 gold badges73 silver badges99 bronze badges 9
  • 1 have a look at require.js – Donal Commented Aug 15, 2014 at 19:04
  • One idea is to embeded both jQuery and AngularJS inside your se_script.js, and make use of .noConflict(). – runTarm Commented Aug 16, 2014 at 11:04
  • @runTam, how does that work if the client app already has jQuery, in a different version than yours? can you prove this idea through a working example :) – oma Commented Aug 16, 2014 at 14:11
  • @Donal please elaborate, thanks. – oma Commented Aug 16, 2014 at 14:13
  • Have a look at web storage HTML5 and javascript, web pages can store data locally within the user's browser. – akio Commented Aug 20, 2014 at 9:19
 |  Show 4 more ments

8 Answers 8

Reset to default 6 +500

RequireJS allows you to create consumable JQuery / AngularJS Apps (AMDs) without worrying about version conflicts

Rather than worrying about library noconflict's, RequireJS creates a separate sandbox / variable scope outside the global with an AMD. Within AMD, only included versions are available.

AMD - Asynchronous Module Definitions are self-enclosed applications with their own scope for libraries. Self-Encolsure is like namespacing, plete no-conflict. Refer to WHY WEB MODULES for a detailed explanation for using modules.

An AngularJS example, create an AngularMain.js file:

require.config({
    baseUrl: "js",    
    paths: {
       'angular': '//ajax.googleapis./ajax/libs/angularjs/1.2.16/angular.min',
       'angularAMD': 'lib/angularAMD.min',
       'ngload': 'lib/ngload.min'
    },
    shim: { 'angularAMD': ['angular'], 'angular-route': ['angular'] },
    deps: ['AngularApp']
});

Now, require.config has indicated that AngularApp.js should also be included (as you'll see, RequireJS usually assumes ".js" extensions), with dependancies on angular.js and angular-route.js

You now define your Angular App and Controllers using a bination of RequireJS and AngularJS syntax in your AngularApp.js file:

define(['angularAMD', 'angular-route'], function (angularAMD) {
    var app = angular.module("webapp", ['ngRoute']);
    app.config(function ($routeProvider) {
        $routeProvider.when("/home", angularAMD.route({
            templateUrl: 'views/home.html', controller: 'HomeCtrl',
            controllerUrl: 'ctrl/home'
        }))
    });
    return angularAMD.bootstrap(app);
});

define(['app'], function (app) {
    app.controller('HomeCtrl', function ($scope) {
        $scope.message = "Message from HomeCtrl"; 
    });
});

Finally: your HTML page requests references you app with a reference to require.js and a data-main attribute indicating app file.

<script data-main="js/AngularMain" src=".../require.js"></script>

Once again: the ".js" file extension is optional


  • much of this code example borrowed from marcoslin.github.io

  • tnajdek has an angular-requirejs-seed Git Hub repo for building angular apps with require pre-configured.

  • RequireJS Official Site has a good tutorial on creating a JQuery AMD.

You can check whether jQuery is loaded correctly by following code,

if (typeof jQuery != 'undefined'){
    // jQuery related code
}

You can detect whether angular is loaded correctly by using following code,

if(typeof angular != 'undefined') {
    // angular related code
}

So there wont be any conflicts in the code, if jQuery is there, jQuery code will run. And if Angular is there, Angular code will run.

You can use jQuery noconflict to ensure that $ wont conflict with any other library

(function($){
   // jQuery code here will not conflict with any other codes.
 })(jQuery); 

If you have multiple versions of angular, following code will help you to solve the conflicts. More Details

(function() {
  // Save a copy of the existing angular for later restoration
  var existingWindowDotAngular = window.angular;

  // create a new window.angular and a closure variable for 
  // angular.js to load itself into
  var angular = (window.angular = {});

  /*
   * Copy-paste angular.js and modules here. They will load themselves into
   * the window.angular object we just created (which is also aliased as
   * "angular")
   */

  ..

  // notice this refers to the local angular variable declared above, not
  // window.angular
  angular.module('MyWidget', ['ngSanitize']);

  ...

  //Manually bootstrap so the old angular version doesn't encounter
  //ng-app='MyWidget' and blow up
  angular.element(document).ready(function() {
    angular.bootstrap(document.getElementById('my-widget', ['MyWidget']);

    // restore the old angular version
    window.angular = existingWindowDotAngular;
  });
});

You can use following function to add custom script to the page,

function loadScript(url, callback) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    script.onreadystatechange = callback;
    script.onload = callback;
    document.body.appendChild(script);
}

and for the jQuery code you can try the following code

var myCode = function () {
    var jQuery_your_version = $.noConflict(true);
    (function ($) {
        // your custom code will go inside this section
        $("#output").append("New code with version: " + $.fn.jquery + "<br/>");
    }(jQuery_your_version));

    $("#output").append("New code with version: " + $.fn.jquery + "<br/>");
};

if (typeof jQuery == 'undefined' || $.fn.jquery != "1.11.1") {
    loadScript("http://code.jquery./jquery-1.11.1.min.js", myCode);
} else {
    myCode();
}

$("#output").append("Old code with version: " + $.fn.jquery + "<br/>");

Demo

For the angular code,

var myCode = function (ang, oldAngular) {
    $("#output").append("New custom code with version: " + ang.version.full + "<br/>");
    window.angular = angular = oldAngular;

    // this is after your code and it is refering to the old angular
    $("#output").append("New code with version: " + angular.version.full + "<br/>");
};

if (typeof angular == 'undefined' || angular.version.full != "1.2.20") {
    var existingWindowDotAngular = window.angular;
    var angular = (window.angular = {});
    loadScript("http://cdnjs.cloudflare./ajax/libs/angular.js/1.2.20/angular.js", function () {
        myCode(angular, existingWindowDotAngular);
    });
} else {
    myCode(angular, angular);
}

Demo

EDIT

Some more details about angular code for better understanding,

var existingWindowDotAngular = window.angular; will load the existing angular object to a variable then var angular = (window.angular = {}); will reset the existing angular object.

next code loadScript() will load the required angular version and it will create new angular object with new version. then in the callback function, we can do the work you need. so if you see the Demo, you will see it will display the new version then we reset the window.angular to old version using window.angular = angular = oldAngular; so in the Demo, now it will display the old version.

Immediately-Invoked Function Expression (IIFE)

A mon pattern in JavaScript is the immediately-invoked function expression. This pattern creates a function expression and then immediately executes the function. This pattern is extremely useful for cases where you want to avoid polluting the global namespace with code – no variables declared inside of the function are visible outside of it.

Source: http://stage.learn.jquery./javascript-101/functions/#immediately-invoked-function-expression-iife

Protecting the $ Alias and Adding Scope

The $ variable is very popular among JavaScript libraries, and if you're using another library with jQuery, you will have to make jQuery not use the $ with jQuery.noConflict(). However, this will break our plugin since it is written with the assumption that $ is an alias to the jQuery function. To work well with other plugins, and still use the jQuery $ alias, we need to put all of our code inside of an Immediately Invoked Function Expression, and then pass the function jQuery, and name the parameter $:

(function ( $ ) {

    $.fn.greenify = function() {
        this.css( "color", "green" );
        return this;
    };

    $.ltrim = function( str ) {
        return str.replace( /^\s+/, "" );
    };

    $.rtrim = function( str ) {
        return str.replace( /\s+$/, "" );
    };

}( jQuery ));

source: http://learn.jquery./plugins/basic-plugin-creation/

When you look at the IIFE structure you will see something like:

(function($){})( jQuery ));

The jQuery variable is the global object that you are passing to the function so the $ sign will be always a 'clean' jQuery variable in the rest of your function. If I was you I would learn this pattern and use it in all your JavaScript to avoid conflicts. Ofcourse, the site that is using your script(s) have to include jQuery(or anyother library you want to use).

To show you how it works I have created a jsFiddle.


Update

I get your point. You want to include your library by yourself so you will never have conflicts. When something sounds to good to be true it usually is. You are not the first one who wants this. The 'problem' is called CORS. You can't include something from another (sub)domain without permission(could be set server side). This is really a security issue. I did my best to show you this in my Fiddle.

With the given libraries noConflict() function it is easy to restore everything, after usage.

The jquery part is easy already: http://api.jquery./jquery.noconflict/

When you run this:

jQuery.noConflict( true );

Jquery cleans everything from the global name space.

The angular situation is less clear. There is a related pull request:

https://github./angular/angular.js/pull/8596

Which means you have to wait them to merge this to stable, or you have to do this manually. Anyway this will restore the pre-angular state:

angular.noConflict();

If you want to create one big file, it should be something similar:

  1. Insert the jquery source. or
  2. Insert the angular.js source (with the PR applied)
  3. Do whatever you like with them.
  4. And restore the original state with these 2 lines:

Code:

jQuery.noConflict( true );
angular.noConflict();

The approach described below should run at any web page where javascript is enabled. Library agnostic. No dependencies. Utilizing a jsonp type approach.

Create a form element to customize "Call-To-Action" settings. Save the selected / created settings to a json object - or, create un-saved on-the-fly. Including global identifier for the specific client and your own database.

Incorporate the saved settings into a IIFE , called by script at client page , resulting in a jsonp call.

i.e.g.,

After collecting settings at a form , saving to a json object , pleted iife at server / service could appear as

(function() {
  var button = document.createElement("button"); // jsonObj.outerElem
  var link = document.createElement("a"); // jsonObj.innerElem
  link.href = "#"; // jsonObj.href  
  link.innerText = "Call To Action!"; //  jsonObj.label
  link.style.textDecoration = "none"; // jsonObj.styles
  button.appendChild(link);  // order jsonObj elems for DOM display
  button.style.fontSize = "36px"; // jsonObj.styles
  return callback(button) // return jsonObj elems
}());

the above file name would include the global identifier for the settings saved at the json object for that client , e.g., "cta-123.js" , where 123 would be the global [client-id] for the client and/or the client's customized json object and/or callback function.

At the client side , 2 script tags , in the below order

<script type="text/javascript">
// define global `callback` function
// `cta` : `Call-To-Action` `button`, `form` , `element` , etc.
callback = function(cta) {
  // do stuff with `cta`
  document.body.appendChild(cta)
};
</script>
<script type="text/javascript" src="https://cta-service/cta-[client-id].js">

Resulting in the "Call-To-Action" element being appended to element defined by client at callback

jsfiddle http://jsfiddle/guest271314/rgokqyfp/

"Namespace" your code in a closure and only include any libraries if they don't already exist in the page. The best way to do that would be as such..

some_script.js

(function(root) {

    function includeJQuery() {
        var s = root.document.createElement('script');
        s.src = "//code.jquery./jquery-1.11.1.min.js";
        root.document.getElementsByTagName('head')[0].appendChild(s);
    }

    function waitForJQuery() {
        if(root.jQuery === undefined || root.jQuery.fn.jquery !== "1.11.1") {
            root.setTimeout(waitForJQuery, 100);
        } else {
            var jQ = jQuery.noConflict(true);
            initMyApplication(jQ);
        }        
    }

    if(root.jQuery === undefined || root.jQuery.fn.jquery !== "1.11.1") {
        includeJQuery();
        waitForJQuery();
    } else {
        initMyApplication(root.jQuery);
    }

    function initMyApplication(jQuery) {
        //
        // YOUR CODE HERE
        //
    }

}(window));

Remember to change any occurrences of window in your code to root and any functions that you want to be accessed globally will need to be properties of root.

For example: if you wanted the function createCtaModal() to be called by the user, you would define it in code as:

root.createCtaModal = function() {
    ...
}

EDIT

An edit has been made so you can use a specific version of jQuery...

fiddle: http://jsfiddle/2rg7cq26/2/

What about:

  1. Using JSONP to load your version of jQuery (See http://jquery-howto.blogspot./2013/09/jquery-cross-domain-ajax-request.html)
  2. Using IIFE as follows

    (function ( $ ) { /* your JQUERY code goes here */ }) (jQuery.noConflict(true));

We are utilizing the fact that http://api.jquery./jquery.noconflict/ returns reference to the JQuery function.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论