I've written a little mobile web application to control YouTube on my PC from my phone, however something strange is happening when searching using the YouTube API. The first time the page loads, everything works great - enter the search term, click search and results are returned.
However, if I click onto another page and then come back, the search no longer works and I see "Uncaught TypeError: Cannot read property of 'search' undefined" in the search function below.
I'm very new to JavaScript so feel free to berate the code, but I've been seeing this problem for a while and despite much googling haven't been able to find a solution.
// Called automatically when JavaScript client library is loaded.
function onClientLoad()
{
//
try
{
gapi.client.load('youtube', 'v3', onYouTubeApiLoad);
}
// Called automatically when YouTube API interface is loaded.
function onYouTubeApiLoad()
{
gapi.client.setApiKey('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
}
function search(q) {
// Create api request and execute it
var request = gapi.client.youtube.search.list({
type: 'video',
part: 'snippet',
q: q
});
// Send the request to the API server,
// and invoke onSearchRepsonse() with the response.
request.execute(onSearchResponse);
}
function onSearchResponse(response) {
showResponse(response);
}
The link to the API script is in my search.aspx page as below:
<script src="https://apis.google.com/js/client.js?onload=onClientLoad" type="text/javascript"></script>
JQuery is also being used, so I don't know if there is any funny business being caused there but any ideas at this point would be very much appreciated!
Make sure you're calling search() after onYouTubeApiLoad() executes.
If you are binding search() to a click event, make sure to do so on the callback:
function onYouTubeApiLoad()
{
gapi.client.setApiKey('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
$("button#search").on("click", function(){ search(...); })
}
Looks like I figured it out. It looks like the initial load of the API is done when it first loads the script in
<script src="https://apis.google.com/js/client.js?onload=onClientLoad" type="text/javascript"></script>
but then not loaded again when leaving and coming back to the page. I added onClientLoad(); to the
$( document ).ready function at it seems to be working now.
Related
What I am trying to do?
I am trying to create a small javascript snippet that would run in my browser on youtube's subscriptions page (https://www.youtube.com/feed/subscriptions) and would allow to bulk add videos I have not watched into my "Watch Later" playlist so I can binge watch them in chunks and/or on my smartTV later.
How am I trying to do it?
I am trying to use Google Youtube Data API and modify "Watch Later" playlist by calling "insert" method.
What is the issue I am getting?
In order to do everything from above one of the things is to load google api script onto the page. And that is where I am seeing issue. When I load that script (https://apis.google.com/js/api.js) on a separately hosted HTML page (or just in jsfiddle sandbox) everything works:
<script>
function handleClientLoad() {
// Load the API client and auth2 library
gapi.load('client:auth2', initClient);
}
function initClient() {
// do nothing for now
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="handleClientLoad()"></script>
However, when I try to do the same in my userscript using tampermonkey I am getting an error.
function loadScript(url, callback)
{
console.log('load script: ' + url);
// Adding the script tag to the body
var body = document.getElementsByTagName('body')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.async = true;
script.defer = true;
// Then bind the event to the callback function.
script.onreadystatechange = function() {
console.log('in readyStateChange. readyState: ' + this.readyState);
callback();
};
script.onload = function() {
console.log('in onload');
this.onload = function() {};
callback();
};
// Fire the loading
body.appendChild(script);
}
function initGAPI() {
console.log('initGAPI');
gapi.load('client:auth2', initClient);
}
function initClient() {
// do nothing for now
}
loadScript('https://apis.google.com/js/api.js', initGAPI);
I can see that my user script is being successfully triggered when I navigate to https://www.youtube.com/feed/subscriptions, however when it comes to gapi.load() method I am getting this error:
Uncaught TypeError: gapi.loaded_0 is not a function
at cb=gapi.loaded_0:1
I tried to inspect what is available on youtube's page before I load my own instance of gapi and realized there is already gapi object exists there and it has only one method: load(). When I try to call that method on that existing object (without trying to load my own instance of gapi script) it:
gapi.load('client:auth2', function() {console.log('gapi loaded');})
I get error of:
GET https://apis.google.com/_/scs/abc-static/_/js/k=gapi.gapi.en.HtLvrA_npCQ.O/m=client/exm=oauth2/rt=j/sv=1/d=1/ed=1/am=AAE/rs=AHpOoo8wHQU_A1WtgGgcOpQEfGjHuD8e-g/cb=gapi.loaded_1 net::ERR_ABORTED somewhere in desktop_polymer.js (line 2661)
I see there are different callbacks engaged (gapi.loaded_0 and gapi.loaded_0). However I can't make anything of it.
I am starting to think that I can't really use Google API while on google's own site (like youtube in my case). Is that correct assumption?
Maybe there are already existing solutions that achive my goal (bulk add non-watched videos into "Watch Later" playlist) - would appreciate any pointers :)
Turned out it was just my lazy eye. I missed the fact that I load https://apis.google.com/js/api.js twice. One time through #require external dependency - feature of tampermonkey script, another time - via dynamically adding that same script to the page as per instructions I started with to onboard with Google API.
Just loading via #require doesn't really work as that doesn't trigger callback that is necessary to instantiate everything properly. So I had to remove that directive and only rely on adding script to the page dynamically. After I did that - everything started to work. So, all in all, my code I posted in question is actually valid one to use, the problem was only in how I adopted it for usage in tampermonkey userscript :)
we have the following situation:
in default.aspx we have a link:
test.
and the JS code:
function doPost() {
$.post('AnHttpHandlerPage.aspx',"{some_data:...}", function(data) {
if(data.indexOf("http://")==0)
window.open(data);
else{
var win=window.open();
with(win.document) {
open();
write(data); //-> how to execute this HTML code? The code also includes references to other js files.
close();
}
}
}).error(function(msg){document.write(msg.responseText);});
}
The callback can first be an url address or 2nd html code that must be executed.
Option 1 fits, but in option 2, a new window will be opened where the code has been written but not executed.
It's clear, since it happens in the stream, it can't be executed. So the question, how can you fix it? Maybe a refresh(), or similar?
Because of the requirement of the customer, the workflow can not be changed, so it must be solved within doPost().
EDIT
The response in case 2 is HTML like this. This part should be executed:
<HTML><HEAD>
<SCRIPT type=text/javascript src="http://code.jquery.com/jquery-latest.js">
</SCRIPT>
<SCRIPT type=text/javascript>
$(document).ready(function() {
do_something...
});
</SCRIPT>
</HEAD>
<BODY>
<FORM>...</FORM>
</BODY>
</HTML>
Please help. Thanks.
In your JS code it should be something like this:
function doPost() {
$.post('AnHttpHandlerPage.aspx',"{some_data:...}", function(data) {
//if(data.indexOf("http://")==0)
if (data.type!="url") //i will add a data type to my returned json so i can differentiate if its url or html to show on page.
window.open(); // I dont know why this is there. You should
else{
var win=window.open(data.url); //This data.url should spit out the whole page you want in new window. If its external it would be fine. if its internal maybe you can have an Action on one of your controllers that spit it with head body js css etc.
/* with(win.document) {
open();
write(data); //-> how to execute this HTML code? The code also includes references to other js files.
close(); */ // No need to write data to new window when its all html to be rendered by browser. Why is this a requirement.
}
}
}).error(function(msg){document.write(msg.responseText);});
}
The overall logic is this
You do your ajax call on doPost
Find out if data returned is of type url or anything that need to open in new window
If it is url type it would have a url (check if this is not null or empty or even a valid url) then open a new window with that url. Have a read of W3C window.open for parameters
If you want to open and close it for some reason just do that by keeping the window handle but you can do this on dom ready event of that new window otherwise you might end up closing it before its dom is completely loaded. (someone else might have better way)
If its not url type then you do your usual stuff on this page.
If this does not make sense lets discuss.
Currently I am using message passing to send a request from my contentscript for data in localStorage and I am not having any issues with that, the content script is working as expected.
Can you do this in the other direction?
I have an object that exists in the content script that has a method called ".apply()" and I want to run it when the used clicks the option to do so.
I tried to make a listener in the content script like this:
var myLinker = new Linker();
chrome.extension.onRequest.addListener(function(request) {
if (request.method == "apply")
{
myLinker.apply("nothing");
alert("applied");
}
else
; //Do nothing
And send requests to it like this:
chrome.extension.sendRequest({method: "apply"}, function(){
alert("Tried to request");
});
I get that it is a bit of a hack, but it is the only thing I could think of, and it doesn't even work =/
Is there a way to do this?
I am pretty sure I could just inject new code into the page from the popup (I think I saw an api function for that), and then run stuff, but that would take more memory and just feels like a bad way to do it, because you would basically have the exact same code twice.
To send a message from the extension to a content script, use chrome.tabs.sendMessage instead of chrome.extension.sendRequest.
Because sendRequest has been superseded by onMessage in Chrome 20, there's no official documentation for sendRequest any more. The documentation for chrome.tabs.sendMessage can be found here. Please note that these events cannot be mixed, use either *Request or *Message.
Yes, you would use this: http://developer.chrome.com/extensions/tabs.html#method-sendMessage
Content scripts live within the DOM of the page. And each page that is open within Chrome has a tab ID associated with it -- http://developer.chrome.com/extensions/tabs.html#type-tabs.Tab
Let's say you want to send the {method: "apply"} to a page that was just opened in a new tab:
chrome.tabs.onCreated.addListener(function(tab) {
chrome.tabs.sendMessage(tab.id, { method: "apply" });
});
There are other events/methods to get the specific Tab you want to send the message to. I think there's one called getCurrent to send to the currently selected tab, check out the docs for that.
With the follow code, the latest tweet is only occasionally showing in Chrome but always in Firefox. Typically only shows in Chrome with /? on the url but vanishes when I refresh.
jQuery(document).ready( function(){
console.log("getting twitter data..");
jQuery.getJSON("http://twitter.com/statuses/user_timeline/*hidden*.json?callback=?", function(data) {
console.log("got it..", data);
jQuery("#tweet").html(data[0].text);
jQuery("#ttime").html(data[0].created_at);
} );
});
You are trying to get the direct JSON from the twitter API. This is not possible due the same origin policy. You are only allowed to get JSON from your own domain.
A workaround exists and it's called JSONP (JSON with padding) and that's what twitter is using. You need to append the name of a function to the callback parameter in the URL. This function then gets executed when the twitter API loads.
For example you could do it like this:
JavaScript
function render(data) {
// data is the object that contains your twitter data. It's an usual JavaScript object.
}
HTML
<script type="text/javascript" src="http://twitter.com/statuses/user_timeline/username?
callback=render"></script>
Make sure that render is already loaded when the twitter API loads.
I'm currently programming in JSP and Javascript. (I am by no means an expert in either). Right now, what I want is for a Javascript function to be called repeatedly and one of the variables to be queried from the database repeatedly (it is the date that the page was last modified). If this variable is greater than when the page was loaded, I want the page to refresh.
What I have so far:
...
<body onload="Javascript:refreshMethod()">
<script type="text/JavaScript">
<!--
function refreshMethod()
{
var interval = setInterval("timedRefresh()", 10000);
}
function timedRefresh() {
var currenttime = '<%=currentTime%>';
var feedlastmodified = '<%=EventManager.getFeedLastModified(eventID)%>';
var currenttimeint = parseInt(currenttime);
var feedlastmodifiedint = parseInt(feedlastmodified);
if(feedlastmodifiedint > currenttimeint)
{
alert(feedlastmodifiedint);
setTimeout("location.reload(true);",timeoutPeriod);
}
if(feedlastmodifiedint < currenttimeint)
{
alert(feedlastmodifiedint + " : " + currenttimeint);
}
}
// -->
</script>
The problem is that everytime the timedRefresh runs, the feedlastModifiedInt never changes (even if it has been changed).
Any help would be appreciated,
Thanks.
The JSP code within the <% ... %> tags runs only once, on the server-side, when the page is loaded. If you look at the source of the page in the browser, you will find that these values have already been placed within the JavaScript code, and thus they will not change during each timer interval.
To update the data as you are expecting, you can use AJAX. You can find plenty of tutorials online.
JSP and JavaScript doesn't run in sync as you seem to expect from the coding. JSP runs at webserver, produces a bunch of characters which should continue as HTML/CSS/JS and the webserver sends it as a HTTP response to the webbrowser as response to a HTTP request initiated by the webbrowser. Finally HTML/CSS/JS runs at the webbrowser.
If you rightclick the page in webbrowser and choose View Source, you'll probably understand what I mean. There's no single line of Java/JSP code. It has already done its job of generating the HTML/CSS/JS. The only communication way between Java/JSP and JavaScript is HTTP.
You need to move this job to some servlet in the server side and let JS invoke this asynchronously ("in the background"). This is also known as "Ajax". Here's a kickoff example with a little help of jQuery.
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).ready(function() {
var refreshInterval = setInterval(function() {
$.getJSON('refreshServlet', function(refresh) {
if (refresh) {
clearInterval(refreshInterval);
location.reload(true);
}
});
}, 10000);
});
</script>
Where the doGet() method of the servlet which is mapped on an url-pattern of /refreshServlet roughly look like this:
response.setContentType("application/json");
if (EventManager.getFeedLastModified(eventID) > currentTime) {
response.getWriter().write("true");
} else {
response.getWriter().write("false");
}
See also:
Communication between Java/JSP/JSF and JavaScript