I'm currently trying to use the YouTube API as part of a jQuery plugin and I've run into a bit of a problem.
The way the YT api works is that you load the flash player and, when it's ready it will send a call back to a global function called onYouTubePlayerReady(playerId). You can then use that id combined with getElementById(playerId) to send javascript calls into the flash player (ie, player.playVideo();).
You can attach an event listener to the player with player.addEventListener('onStateChange', 'playerState'); which will send any state changes to another global function (in this case playerState).
The problem is I'm not sure how to associate a state change with a specific player. My jQuery plugin can happily attach more than one video to a selector and attach events to each one, but the moment a state actually changes I lose track of which player it happened in.
I'm hoping some example code may make things a little clearer. The below code should work fine in any html file.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="application/text+html;utf-8"/>
<title>Sandbox</title>
<link type="text/css" href="http://jqueryui.com/latest/themes/base/ui.all.css" rel="stylesheet" />
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("jquery", "1.3.2");
google.load("jqueryui", "1.7.0");
</script>
<script type="text/javascript" src="http://swfobject.googlecode.com/svn/tags/rc3/swfobject/src/swfobject.js"></script>
<script type="text/javascript">
(function($) {
$.fn.simplified = function() {
return this.each(function(i) {
var params = { allowScriptAccess: "always" };
var atts = { id: "ytplayer"+i };
$div = $('<div />').attr('id', "containerplayer"+i);
swfobject.embedSWF("http://www.youtube.com/v/QTQfGd3G6dg&enablejsapi=1&playerapiid=ytplayer"+i,
"containerplayer"+i, "425", "356", "8", null, null, params, atts);
$(this).append($div);
});
}
})(jQuery);
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', 'playerState');
}
function playerState(state) {
console.log(state);
}
$(document).ready(function() {
$('.secondary').simplified();
});
</script>
</head>
<body>
<div id="container">
<div class="secondary">
</div>
<div class="secondary">
</div>
<div class="secondary">
</div>
<div class="secondary">
</div>
</div>
</body>
</html>
You'll see the console.log() outputtin information on the state changes, but, like I said, I don't know how to tell which player it's associated with.
Anyone have any thoughts on a way around this?
EDIT:
Sorry, I should also mentioned that I have tried wrapping the event call in a closure.
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', function(state) {
return playerState(state, playerId, player); } );
}
function playerState(state, playerId, player) {
console.log(state);
console.log(playerId);
}
In this situation playerState never gets called. Which is extra frustrating.
Edit:
Apparently calling addEventListener on the player object causes the script to be used as a string in an XML property that's passed to the flash object - this rules out closures and the like, so it's time for an old-school ugly hack:
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', '(function(state) { return playerState(state, "' + playerId + '"); })' );
}
function playerState(state, playerId) {
console.log(state);
console.log(playerId);
}
Tested & working!
Im Using Jquery SWFobject plugin, SWFobject
It is important to add &enablejsapi=1 at the end of video
HTML:
<div id="embedSWF"></div>
Jquery:
$('#embedSWF').flash({
swf: 'http://www.youtube.com/watch/v/siBoLc9vxac',
params: { allowScriptAccess: "always"},
flashvars: {enablejsapi: '1', autoplay: '0', allowScriptAccess: "always", id: 'ytPlayer' },
height: 450, width: 385 });
function onYouTubePlayerReady(playerId) {
$('#embedSWF').flash(function(){this.addEventListener("onStateChange", "onPlayerStateChange")});
}
function onPlayerStateChange(newState) {
alert(newState);
}
onYouTubePlayerReady must be outside of $(document).ready(function() to get fired
I had this same problem and tried the accepted answer. This didn't work for me; the playerState() function was never called. However, it put me on the right path. What I ended up doing was this:
// Within my mediaController "class"
window["dynamicYouTubeEventHandler" + playerID] = function(state) { onYouTubePlayerStateChange(state, playerID); }
embedElement.addEventListener("onStateChange", "dynamicYouTubeEventHandler" + playerID);
// End mediaController class
// Global YouTube event handler
function onYouTubePlayerStateChange(state, playerID) {
var mediaController = GetMediaControllerFromYouTubeEmbedID(playerID);
mediaController.OnYouTubePlayerStateChange(state);
}
It's fairly nasty, but so is the current state of the YouTube JavaScript API.
Here is some other helpful/nasty code if you are using any kind of advanced prototyping patterns. This basically allows you to retrieve a "class" instance from the YouTube player ID:
// Within my mediaController "class"
// The containerJQElement is the jQuery wrapped parent element of the player ID
// Its ID is the same as the player ID minus "YouTubeEmbed".
var _self = this;
containerJQElement.data('mediaController', _self);
// End mediaController class
// Global YouTube specific functions
function GetMediaControllerFromYouTubeEmbedID(embedID) {
var containerID = embedID.replace('YouTubeEmbed', '');
var containerJQObject = $("#" + containerID);
return containerJQObject.data('mediaController');
}
function onYouTubePlayerReady(playerId) {
var mediaController = GetMediaControllerFromYouTubeEmbedID(playerId);
mediaController.OnYouTubeAPIReady();
}
Here's a nice article that goes through creating a class to wrap an individual player, including dispatching events to individual objects using a similar approach to that mentioned in a previous answer.
http://blog.jcoglan.com/2008/05/22/dispatching-youtube-api-events-to-individual-javascript-objects/
How about something like so:
var closureFaker = function (func, scope) {
var functionName = 'closureFake_' + (((1+Math.random())*0x10000000)|0).toString(16);
window[functionName] = function () {
func.apply(scope || window, arguments);
};
console.log('created function:', functionName, window[functionName]);
return functionName;
};
ytplayer.addEventListener("onStateChange", closureFaker(function () {
//place your logic here
console.log('state change', arguments)
}));
Related
The situation : I use a script (a) in an HTML document to be able to use a particular SDK. Then I use a second script (b) basic to be able to create a Kendo UI table.
My problem : I try to pass data from script (a) to script (b) via global variables but it doesn't work, what can I do?
Info that might help you:
my document is a form framed by tags
I use Camunda. The first script allows me to use the SDK to retrieve the ID of the instance associated with the form being processed. (but I don't think this is the crux of the problem)
I assume that both scripts are read at the same time by the browser, and that's why script (b) can't read the variable simply because it is not yet created in script (a).
The code :
<script type="text/javascript" src="http://localhost:8080/camunda/app/tasklist/scripts/kendo.all.min.js"></script>
<script cam-script type="text/form-script">
var taskService = camForm.client.resource('task');
var processInstanceId = null;
var result = null;
taskService.get(camForm.taskId, function(err, task) {
//alert(JSON.stringify(task));
debugger;
processInstanceId = task.processInstanceId;
$.get("http://localhost:8080/engine-rest/process-instance/"+processInstanceId+"/variables", function(result) {
debugger;
window.alert("coucou");
console.log(result.JsonWeightSetpoints.value);
});
debugger;
console.log(result.JsonWeightSetpoints.value);
debugger;
});
</script>
<script type="text/javascript">
console.log(result.JsonWeightSetpoints.value);
//this is where I implement the Kendo UI grid
</script>
<div id="grid"></div>
I cannot read the content of the result variable in script (b) because it is not defined.
How do I do this?
Custom events solution:
<script type="text/javascript" src="http://localhost:8080/camunda/app/tasklist/scripts/kendo.all.min.js"></script>
<script cam-script type="text/javascript">
var taskService = camForm.client.resource('task');
var processInstanceId = null;
var result = null;
taskService.get(camForm.taskId, function(err, task) {
//alert(JSON.stringify(task));
debugger;
processInstanceId = task.processInstanceId;
$.get("http://localhost:8080/engine-rest/process-instance/"+processInstanceId+"/variables", function(result) {
debugger;
window.alert("coucou");
console.log(result.JsonWeightSetpoints.value);
// the value is available here, we now can trigger an event and send it
document.dispatchEvent(new CustomEvent("handler", {
detail: { value: result.JsonWeightSetpoints.value }
}));
});
debugger;
console.log(result.JsonWeightSetpoints.value);
debugger;
});
</script>
<script type="text/javascript">
console.log(result.JsonWeightSetpoints.value);
//this is where I implement the Kendo UI grid
// event listener, this would get executed only when we want
document.addEventListener("handler", function(event) {
// write you logic here
console.log(event.detail.value);
});
</script>
<div id="grid"></div>
useful resources:
MDN: Creating and triggering
events
Introducing asynchronous JavaScript
I have a single-page html application. There is only one thml file that has multiple DIVs, that are displayed/hidden when the relevant button is clicked.
I use some animation on one of this DIVs (not on the "first page DIV"). The problem is that the animation starts directly when the html document is loaded and vhen i go to that div with the animation, the animation is already ended.
My question is: How to make the animation start just at the moment when it's DIV is displayed?
here is the code:
<!DOCTYPE html>
<html>
<head>
<script>
function show(shown, hidden) {
document.getElementById(shown).style.display='block';
document.getElementById(hidden).style.display='none';
return false;
}
</script>
<script>
function myFunction()
{
var o, qt=[
["a","b","c"],
["d","e","f"],
["g","h","i"]];
o=document.getElementById("quote1");
o.innerHTML="";
for(var i=1;i<4;i++)
o.innerHTML+="<p id=\"quote1_"+i+"\" style=\"font-size: 28pt;\"> </p>";
var q=qt[Math.floor(Math.random() * qt.length)];
document.getElementById("quote1_1").innerHTML=q[0];
setTimeout(function() { document.getElementById("quote1_2").innerHTML=q[1]; }, 2000);
setTimeout(function() { document.getElementById("quote1_3").innerHTML=q[2]; }, 3000);
}
window.onload = function(){
myFunction();
}
</script>
</head>
<body>
<div id="Page1">
<div data-role="page">
<div data-role="content">
<center>
SHOW ANIMATION
</center>
</div>
</div>
</div>
<div id="Page2" style="display:none">
<div data-role="page" id="main">
<div data-role="content">
<center>
<div id="quote1"></div>
<center>
Next
</center>
</center>
</div>
</div>
</div>
</body>
</html>
Adding to Kcent's post, there is also a plugin for jQuery called "ScrollMagic":
http://janpaepke.github.io/ScrollMagic/
It activates animations depending on scroll speed/placement on the page.
You can find the documentation here:
http://janpaepke.github.io/ScrollMagic/docs/index.html
Using pure javascript, you can use the MutationObserver. Yet using libraries like angularjs or knockoutjs should be much simpler. The MutationObserver is quite complicated in my opinion. One way to do that when the MutationObserver didn't exist yet was to track object creation. When you create an object in the dom, you can trigger the callback. The only big problem with this method is that it wouldn't work for changes to the dom that aren't explicitly tracked. The MutationObserver will catch any change to the DOM by user interaction or not.
In your case, a simple solution would be to execute something right after you added it to the dom.
Something like that:
function changeElement(target, source, callback) {
target.innerHTML = source;
setTimeout(function() {
callback(target);
}, 0);
}
setTimeout(function() {
changeElement(document.getElementById("quote1_2"), q[1], function (parent) {
// code here
}
}, 2000);
You could also change the function above to already include the setTimeout to make it cleaner.
function changeElement(target, source, callback, wait) {
setTimeout(function() {
target.innerHTML = source;
setTimeout(function() {
callback(target);
}, 0);
}, wait);
}
changeElement(document.getElementById("quote1_2"), q[1], function (parent) {
// code here
}, 2000);
The setTimeout with 0 is to make sure the code is executed after the innerHTML gets updated. It's possible that if you execute directly the callback, the dom won't be already available in the callback.
As for animation, you should have a look to css3 transitions. https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_transitions The animations can be done by making opacity modified itself slowly for example. It really depends on what you want to do.
MutationObserver
This is a way to do what you're trying to do. MutationObserver isn't that easy to setup. I did a small example:
var target = window.container;
function changeOpacity(target) {
setTimeout(function(){
target.style.opacity = 1;
}, 100);
}
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation);
if (mutation.type == "childList") {
for(var i=0; i<mutation.addedNodes.length; i++) {
changeOpacity(mutation.addedNodes[i]);
}
}
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
setTimeout(function () {
target.innerHTML = "<div class='elem'>Some text</div>";
}, 2000);
And a fiddle: http://jsfiddle.net/8zmc8/
If you are able to use jQuery, there's a library called waypoints that handles this very problem: http://imakewebthings.com/jquery-waypoints/
The core of what you are looking for is:
- adding an event listener to the window scroll
- listening for the offsetTop of the target div to come into view
- and then trigger the animation at the desired offsetTop value
What am doing wrong. I try to make object but when i try to initialize i get this error in console: I try to put all in document.ready and whitout that but dont work. In both case i have some error. Am new sorry for dumb question
ReferenceError: Circle is not defined
var obj = new Circle;
JS
$(function(){
var Circle = {
init: function() {
console.log("Circle initialized");
}
};
});
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="javascript/circle.js"></script>
<script>
$(document).ready(function(){
var obj = new Circle;
obj.init();
})
</script>
</head>
<body>
<div id="test" >TODO write content</div>
</body>
</html>
NEW UPDATE
$(function(){
window.Circle = {
init: function() {
console.log("Circle initialized");
}
};
window.Circle.init();
});
....
<head>
<script>
window.Circle().init();
</script>
</head>
You've defined your "Circle" function inside another function — the anonymous function you pass in as a a "ready" handler. Therefore, that symbol ("Circle") is private to that function, and not visible to the other code.
You can make it global like this:
window.Circle = {
// ...
};
You could also add it to the jQuery namespace (may or may not be appropriate; depends on what you're doing), or you could develop your own namespace for your application code. Or, finally, you could consider combining your jQuery "ready" code so that the "Circle" object and the code that uses it all appears in the same handler.
edit — another possibility is to move your "Circle" declaration completely out of the "ready" handler. If all you do is initialize that object, and your property values don't require any work that requires the DOM or other not-yet-available resources, you can just get rid of the $(function() { ... }) wrapper.
1) you are assigning Circle in a function context, not as a global. You can only use it there unless you expose it to global.
2) you are calling Circle as a constructor, but Circle is not a function.
This solves both issues:
var Circle = function () {};
Circle.prototype.init = function () {
console.log('Circle initialized.');
};
var obj = new Circle();
obj.init();
I want to see an alert message when the value of a div changes. This value is being modified by modify_div. When I click the button this function modifies the div, but the alert "value changed" is not displayed. Am I missing something?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" " http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var demo = Y.one('#test');
demo.on('click', function (e) {
//alert('You clicked me');
});
});
YUI().use('node','event', function (Y) {
var demo = Y.one('#variable-name');
demo.on('change', function (e) {
alert('Value changed');
});
});
</script>
<script type="text/javascript">
function modify_div()
{
//var thevar = "This is a test";
var thevar = 7;
document.getElementById('variable-name').innerHTML = thevar;
}
</script>
</head>
<body>
<!-- Click me button -->
<input type="button" id="test" value="Click me" enabled="true" onclick="modify_div();"> </input>
</br>
<div id="variable-name" style="display:inline;">01010101</div>
</body>
</html>
based on http://www.quirksmode.org/dom/events/change.html,
change event only fires if its form field
e.g. input textarea and select
so change event will not fire when contents of div is changed.
It will work if you replace div with input and update its value.
other option is to manually fire event where ever you are changing the value your variable
http://tech.groups.yahoo.com/group/ydn-javascript/message/13216
following SO question has answers but it requires jQuery
Detect element content changes with jQuery
The correct answer was given by #N30: there is no change event for divs. He provides good alternatives but no YUI specific information, so I'd like to extend his answer with an example of a YUI Plugin.
Like he explained, the basic idea is to keep a value in JavaScript memory and fire an event when you change that value. You can do this by extending Y.EventTarget which provides you with custom events:
YUI().use('node', 'plugin', function (Y) {
function NodeValuePlugin(config) {
// Boilerplate
NodeValuePlugin.superclass.constructor.apply(this);
// config.host is the Y.Node instance
this._node = config.host;
// we keep the value in a private property
this._value = this._node.get('text');
// we publish a 'change' event and set a default
// function to run when the event is fired
// This function will change the private property
// and update the DOM
// This means you can call e.preventDefault() and
// stop the default behavior (the change of value)
this.publish('change', {
emitFacade: true,
defaultFn: this._defValueChangeFn
});
}
Y.extend(NodeValuePlugin, Y.EventTarget, {
set: function (newVal) {
// we want to do stuff only when the value changes
if (newVal != this._value) {
// instead of changing the DOM here,
// we fire an event and let the event
// do the work
// We pass it the new and old values
this.fire('change', {
newVal: newVal,
prevVal: this._value
});
}
// make this method chainable
return this;
},
get: function () {
return this._value;
},
_defValueChangeFn: function (e) {
// sync everything
this._value = e.newVal;
this._node.set('text', e.newVal);
},
// this is necessary boilerplate for plugins
destroy: function () {}
}, {
// we can access the plugin from node[namespace]
// in this case, node.value
NS: 'value'
});
var node = Y.one('#test').plug(NodeValuePlugin);
node.value.on('change', function (e) {
console.log('Here\'s the old value: ' + e.prevVal);
console.log('Here\'s the new value: ' + e.newVal);
});
// Freebie:
// since we're using node.set('text', foo)
// this prevents XSS vulnerabilities
node.value.set('qwer');
});
You can learn more about plugins from the Plugin User Guide in the YUI website.
REVISED QUESTION (SEE BELOW FOR ORIGINAL):
Here is an example of a simple ajax load with an event binding on an element within the loaded content:
soTest.htm
<!DOCTYPE html>
<html>
<head>
<script language="JavaScript" type="text/javascript" src="http://code.jquery.com/jquery-1.6.min.js"></script>
<script>
function changeBG(obj)
{
alert('Color 1: Should Turn Red');
jQuery(obj).css('background-color','red');
alert('Color 2: Should Turn Green');
jQuery('#' + jQuery(obj).attr('id')).css('background-color','green');
}
jQuery(document).ready(
function() {
jQuery('.loadedContent').load('soTest2.htm');
jQuery('body').delegate("#theElem","click",
function(){
var obj = this;
jQuery('.loadedContent').load('soTest2.htm',
function(){
changeBG(obj);
}
);
});
}
);
</script>
</head>
<body>
<div class="loadedContent">
</div>
</body>
</html>
Ajax loaded content, soTest2.htm:
<div id="theElem" >
Hello
</div>
So why is it that this doesn't work:
jQuery(obj).css('background-color','red');
But this does:
jQuery('#' + jQuery(obj).attr('id')).css('background-color','red');
++++++++++ORIGINAL QUESTION:++++++++++
I have a table that I want to sort when specific table headings are clicked (those with the class "sort").
For instance:
Location
To do that I have this code:
jQuery('body').delegate("click", ".sort", function(event) {
event.preventDefault();
jQuery('.searchResults').html('<div align="center" style="margin-top:35px;"><img src="/common/images/ajax-loader_big.gif" /></div>');
var TimeStamp = new Date().getTime();
var sortItem = this;
jQuery('.searchResults').load('modules/configSearchResultsOutput.cfm?' + TimeStamp + '&sortby=' + jQuery(this).attr('sortby') + '&direction=' + jQuery(this).attr('direction'), {
data: jQuery('#results').val()
}, function() {
sortCallback(sortItem);
});
});
So on the click event for one of these sortable column headings I'm storing the entire 'this' scope in a var to pass through to this function.
To simplify the question I'll just say that we're trying to change the background color of the clicked element based on the custom attr 'direction' I'm using:
function sortCallback(obj) {
//Returns correct attribute value
alert('In Callback: ' + jQuery(obj).attr('direction'));
//Does not return correct attribute value -- almost like it's cached or something
alert('Long hand reference: ' + jQuery('.sort[sortby="' + jQuery(obj).attr('sortby') + '"]').attr('direction'));
//Must reference value via (obj) to get correct updated value
if (jQuery(obj).attr('direction') == 'asc') {
//Changing a value within the element via this longhand approach works
jQuery('.sort[sortby="' + jQuery(obj).attr('sortby') + '"]').css('background-color', 'red');
//Changing a value within the element via this shorter approach does not work
jQuery(obj).css('background-color', 'red');
}
else {
//Works
jQuery('.sort[sortby="' + jQuery(obj).attr('sortby') + '"]').css('background-color', 'green');
//Doesn't work
jQuery(obj).css('background-color', 'green');
}
}
I'm assuming I'm not understanding some aspect of javascript scoping (understanding 'this' has been very elusive to me).
Question summarized:
If I'm passing a var'd 'this' scope to a function why can't I change the aspects of the 'this' element, why must I drill down using the long way to change them?
A tricky question for me to articulate, hopefully I did a good enough job.
Thanks!
This is happening because your ajax call replaces the DOM element. obj refers to a DOM element that was in the DOM before you called .load, but was replaced. Another element with the same ID does exist, though! That's the one you're referring to with your 'longhand' method.
I think your problem is because that load call is asynchronous, causing jQuery to get confused. Put your code inside a callback for load and it should work:
jQuery(document).ready(function() {
jQuery('.loadedContent').load('soTest2.htm',
function(resp, status, xhr){
jQuery("#theElem").bind('click',
function(){
changeBG(this);
});
});
});