Search code examples
javascriptnode.jsexpresspaypalonsen-ui

Onsen UI broke my PayPal script (and all other javascript)


I have been slowly integrating Onsen UI with an existing web app of mine.

My home page file (index.jade) contains a splitter which enables navigation throughout the app. The splitter loads a NodeJS route which renders the requested page in jade. Everything up to this point works as intended (really just all my front end jade code being rendered correctly). Each page of the splitter is stored in a separate jade file (with the exception of the home page).

The Problem:

By configuring my app this way, I have learned through trial and error that all my javascripts in all my jade files are no longer being rendered with the jade code (I do not know the correct terminology). I can only get my javascripts to work if I either cut-and-paste them to index.jade, or create separate javascript files and include them in index.jade.

This is a problem in and of itself (although I can work around it by doing the above), but it is the cause of my main problem: it breaks my PayPal script on one of my pages.

Before the integration of Onsen UI, I was successfully using the PayPal incontext checkout in my app.

Code Samples:

This is how I was implementing PayPal in one of my views before I started integrating Onsen UI:

//- the following is for In-Context PayPal checkout ********************
  div(ng-show="viewEvent && viewEvent.spacesAvailable > 0")
    br
    label(ng-show='eventStatus === 1') This event is being hosted by you! You can not RSVP.  
    label(ng-show='eventStatus === 2') You have RSVP'd for this event, but have not payed.
    label(ng-show='eventStatus === 3') You have already payed for this event.
    label(ng-show='eventStatus === 4') RSVP
    form#myContainer(ng-show='eventStatus === 2 || eventStatus === 4' action="{{'https://10.0.0.25:8001/checkout/' + user._id + '/' + viewEvent._id + '/' + viewEvent.eventName + '/' + viewEvent.price}}" ng-click='rsvp(user._id, viewEvent._id);' method='get')
    script.
      window.paypalCheckoutReady = function () {
        paypal.checkout.setup("**redacted**", {
          environment: "sandbox",
          container: "myContainer"
        });
      };
    script(src='//www.paypalobjects.com/api/checkout.js', async)

I just put the script in there, everything loaded correctly, and I was able to make a purchase with PayPal's incontext checkout. However, now I can not insert the script like that. I've changed my code to this:

//- the following is for In-Context PayPal checkout ********************
  div(ng-if="viewEvent && viewEvent.spacesAvailable > 0")
    br
    label(ng-show='eventStatus === 1') This event is being hosted by you! You can not RSVP.  
    label(ng-show='eventStatus === 2') You have RSVP'd for this event, but have not payed.
    label(ng-show='eventStatus === 3') You have already payed for this event.
    label(ng-show='eventStatus === 4') RSVP
    form#myContainer(ng-show='eventStatus === 2 || eventStatus === 4' action="{{'https://10.0.0.25:8001/checkout/' + user._id + '/' + viewEvent._id + '/' + viewEvent.eventName + '/' + viewEvent.price}}" ng-click='rsvp(user._id, viewEvent._id);' method='get')
    div(ng-init='openPayPal()')

The last div calls a function in the angular controller, openPayPal(), which calls a function in the javascript file that is being included in index.jade:

function openPayPal() {
  window.paypalCheckoutReady = function () {
    paypal.checkout.setup("**redacted**", {
      environment: "sandbox",
      container: "myContainer"
    });
  };
}

Additionally, the PayPal script:

script(src='//www.paypalobjects.com/api/checkout.js', async)

is now included in index.jade.

So after doing all this, the PayPal button does not even appear. I believe this is because I am doing something wrong with the scripts.

I am out of ideas.

Does anyone know how to implement this, or know what I am doing wrong?

Additionally, does anyone know a work around to include javascript in my jade files?

Thank you all for your time.

UPDATE

If I go to index.jade, delete EVERYTHING below body (including the splitter that loads the page where the PayPal button does not work), and paste the following code, then the PayPal button shows up like it is supposed to (like it used to before I started integrating Onsen UI).

form#myContainer(action='*deleted for now*' method='get')
script.
  window.paypalCheckoutReady = function () {
    paypal.checkout.setup("*redacted*", {
      environment: "sandbox",
      container: "myContainer"
    });
  };
script(src='//www.paypalobjects.com/api/checkout.js' async)

UPDATE 2 FOR SOLUTION

I am having trouble loading the script dynamically from my controller. Here is what is in search.jade:

  //- the following is for In-Context PayPal checkout ********************
  div(ng-show="viewEvent && viewEvent.spacesAvailable > 0")
    br
    label(ng-show='eventStatus === 1') This event is being hosted by you! You can not RSVP.  
    label(ng-show='eventStatus === 2') You have RSVP'd for this event, but have not payed.
    label(ng-show='eventStatus === 3') You have already payed for this event.
    label(ng-show='eventStatus === 4') RSVP
    form#myContainer(on-load='controller.openPayPalPlease()' ng-show='eventStatus === 2 || eventStatus === 4' action="{{'https://10.0.0.25:8001/checkout/' + user._id + '/' + viewEvent._id + '/' + viewEvent.eventName + '/' + viewEvent.price}}" ng-click='rsvp(user._id, viewEvent._id);' method='get')

and here is my angular function in the controller:

this.openPayPalPlease = function() {
    window.paypalCheckoutReady = function () {
      paypal.checkout.setup("H6LX5CJXEQYEL", {
        environment: "sandbox",
        container: "myContainer"
      });
    };

    var s = document.createElement('script');
    s.setAttribute('src', '//www.paypalobjects.com/api/checkout.js');
    document.getElementById('myContainer').appendChild(s);
  };

Notice how I am calling the angular function with on-load in the PayPal form in the jade file. Without using on-load, and placing this div under the PayPal form,

div(ng-init='controller.openPayPalPlease()')

I receive the error:

Cannot read property 'appendChild' of null at openPayPalPlease

Using on-load, nothing happens, and the script does not get loaded.


Solution

  • I would guess that due to some circumstances either you aren't calling your function properly, or you are trying to call it before it is ready.

    Sidenote: While this may not be the issue - right now you're calling a global function - in angular there are 2 common ways to do event handling.

    Way 1 (old, but common way)

    module.controller('MyController', function($scope) {
        $scope.myHandler = function() { ... };
    });
    
    <div ng-controller="MyController">
        <div ng-event-name="myHandler()"></div>
    </div>
    

    Way 2 (new, recommended)

    module.controller('MyController', function() {
        this.myHandler = function() { ... };
    });
    
    <div ng-controller="MyController as MC">
        <div ng-event-name="MC.myHandler()"></div>
    </div>
    

    So you could try using one of these ways. And also you can try to do some console.logs in the openPayPal and paypalCheckoutReady functions ready to make sure everything is executed fine. Also you should check if you are getting any errors in the console. (right click → inspect element → console)

    Update:

    I checked the implementation of paypalobjects.com/api/checkout.js and I think I found out what is happening.

    Until this point I was expecting that you were still calling your paypalCheckoutReady function but it seems that actually the checkout script checks if it exists and executes it, otherwise it executes a default function. Since you are the one who defined the function you actually should've been able to add a console.log inside it and see that it wasn't executed.

    Since you said that you moved the paypal script to index.jade that means that it's executed long before your actual code becomes executed. Thankfully the default function which is called doesn't do anything if you don't have elements with attributes data-paypal-(button|id|sandbox) so you're safe.

    The only thing left is to call your function. I checked the implementation of the script, so the only thing which it's doing with your function is calling it after the page has been loaded. So in your case you can just call it yourself. You can just add this line after you have defined your paypalCheckoutReady function like this:

    window.paypal && window.paypalCheckoutReady();
    

    Alternatively you could load the paypal script dynamically from your controller. From a concept level this could be an elegant solution as it provides you with more control over the program flow in general. So something like

    var s = document.createElement('script');
    s.setAttribute('src', '//www.paypalobjects.com/api/checkout.js');
    parent.appendChild(s);
    

    where parent is document.body, document.head, your page or whatever you like. You could also make some checks to prevent loading the script a second time.

    I think both solutions should work, so you can use the one which you prefer.