Hello I'm experimenting with Box2dWeb, and working with top-down car game.
My problem arises when I try to control the car, so it will move, at first only forwards. For simplicity I don't want to use wheels, and just apply the force to the car (a box).
For the controls I made a function for but for a reason it's not getting called... That's where I need a pointer or advice. (Creation and placement of objects works just fine)
Here's part of the code:
var GlobalVar={ }
var KEY = {
UP: 87,//W
DOWN: 83,//s
LEFT: 65,//A
RIGHT: 68//D
}
GlobalVar.pressedKeys = [];//an array to remember which key is pressed or not
$(function(){
$(document).keydown(function(e){
GlobalVar.pressedKeys[e.keyCode] = true;
});
$(document).keyup(function(e){
GlobalVar.pressedKeys[e.keyCode] = false;
});
Rendering();
PlaceStuff(GlobalVar.currentLevel);//placing stuff, like car and boundaries/walls
moveCar();
});
function moveCar(){
if (GlobalVar.pressedKeys[KEY.UP]){
var force = new b2Vec2(0, -10000000);
GlobalVar.car.ApplyForce(force, GlobalVar.car.GetWorldCenter());
}
}
It doesn't look like the moveCar function is being called more than once.
You should do the following:
function moveCar(){
if (GlobalVar.pressedKeys[KEY.UP]){
var force = new b2Vec2(0, -10000000);
GlobalVar.car.ApplyForce(force, GlobalVar.car.GetWorldCenter());
}
requestAnimationFrame(moveCar);
}
You may also want to add a modifier to modify the amount of force added depending on the frame rate:
then = Date.now();
function moveCar(){
var now = Date.now();
var modifier = now - then; // Make modifier the time in milliseconds it took since moveCar was last executed.
then = now;
if (GlobalVar.pressedKeys[KEY.UP]){
var force = new b2Vec2(0, -10000000);
GlobalVar.car.ApplyForce(force * modifier, GlobalVar.car.GetWorldCenter());
}
requestAnimationFrame(moveCar);
}
This will ensure the car doesn't move slower on slower systems.
If you also want the Rendering() function to be executed more than once, you may also want to create another function which gets called as often as possible and calls the other two functions.
then = Date.now();
function moveCar(modifier){
if (GlobalVar.pressedKeys[KEY.UP]){
var force = new b2Vec2(0, -10000000);
GlobalVar.car.ApplyForce(force * modifier, GlobalVar.car.GetWorldCenter());
}
}
function update() {
var now = Date.now();
var modifier = now - then; // Make modifier the time in milliseconds it took since moveCar was last executed.
then = now;
moveCar(modifier);
Rendering();
requestAnimationFrame(update);
}
As pointed out in the comments, you only call moveCall once, but you probably want to do it after each key press:
$(document).on('keydown keyup', function(e){
GlobalVar.pressedKeys[e.keyCode] = true;
moveCar();
});
Related
I would like to create a Chrome extension to change the color of the text for all events in a particular calendar. I am modifying the code in an extension called gcalcolor. Here is the code in the content.js file of the extension:
// Content script for the extension. Does all the work.
"use strict";
// Colorizes an event element. Finds the colored dot, then sets the
// overall color to that dot's color (which is its borderColor; the
// dot is just an empty 0x0 element with a circular border). Also
// hides the dot, since it is no longer needed to show the color, and
// reduces padding to help line up events and let you see more of
// their names.
function colorizeEvent(eventEl) {
let success = true;
// First try layout for month and multi-week (custom) views.
let dotEl = eventEl;
for (let i=0; i<3; i++) {
dotEl = dotEl.firstChild;
if (!dotEl) {
success = false;
break;
}
}
if (success) {
let color = dotEl.style.borderColor;
if (!color) {
success = false; // Probably not a timed event
}
else {
eventEl.firstChild.style.color = color;
eventEl.firstChild.style.padding = '0';
dotEl.style.display = 'none';
}
}
// if the above failed, try the Schedule (Agenda) layout
if (!success) {
let timeEl = eventEl.firstChild;
if (!timeEl) {
return;
}
let detailsEl = timeEl.nextSibling;
if (!detailsEl) {
return;
}
let dotContainer1El = detailsEl.nextSibling;
if (!dotContainer1El) {
return;
}
let dotContainer2El = dotContainer1El.firstChild;
if (!dotContainer2El) {
return;
}
let dotEl = dotContainer2El.firstChild;
if (!dotEl) {
return;
}
let color = dotEl.style.borderColor;
if (!color) {
return;
}
else {
detailsEl.style.color = color;
eventEl.style.height = '28px';
dotContainer1El.style.display = 'none';
}
}
}
// Colorizes all visible events.
function colorizeAll() {
let eventElements = document.querySelectorAll('[data-eventid]');
for (let eventElement of eventElements) {
colorizeEvent(eventElement);
}
}
// We don't have a precise way to know when Google Calendar has drawn
// some new events on the screen. Instead we use a MutationObserver to
// watch for DOM changes anywhere on the page. It would be really
// inefficient to run colorizeAll every time we got an observer
// callback, so instead we wait for a short delay to see if any more
// callbacks happen. If so, we reset the timer and wait again. We call
// colorizeAll only when the timer completes without another callback.
//
// Because there are a lot of irregularly timed screen updates when
// the page is first being loaded, we set the delay to a quarter second
// at first. After five seconds, we set it to 20 milliseconds for a
// faster response to small updates.
let timeoutId = null;
let observerDelay = 250;
setTimeout(() => { observerDelay = 20; }, 5000);
function postObserverCallbacks() {
timeoutId = null;
colorizeAll();
}
function observerCallback(mutationList) {
if (timeoutId)
clearTimeout(timeoutId);
timeoutId = setTimeout(postObserverCallbacks, observerDelay);
}
let observer = new MutationObserver(observerCallback);
observer.observe(
document.body,
{
childList: true,
attributes: true,
subtree: true
}
);
I have already modified the following line as follows:
Original line
let color = dotEl.style.borderColor;
My new line
let color = '#FA8072';
This modification causes the text of all events to change to the same color.
Now, I want to change the color of events only in a particular calendar. I know the id of the calendar. For sake of illustration, let's say the calendar id is abcdefg1234567. I think the line of code I need to modify to select only events from this calendar is this line:
let eventElements = document.querySelectorAll('[data-eventid]');
I have searched for a few hours to try and figure out how to modify this line, but I haven't been able to figure it out.
Simple answer:
Delete "use strict"; at top of page :)
I'm trying to scroll a greensock tween in pixi. I'm getting errors trying to hook the code that gets the mouse/arrow input (trackpad.value) with my tween.
Here's my working greensock test tween, to make sure I have greensock working in pixi: (have to tween the position element in pixi):
var t1 = new TimelineMax({onUpdate:animate, onUpdateScope:stage});
t1.to(bg.position, 3, {y:100});
Here's my code where I'm trying to hook trackpad.value into the greensock code (I'm getting the following error: Uncaught TypeError: bg.position is not a function):
trackpad = new Trackpad(document);
var t1 = new TimelineMax({paused:true, onUpdate:animate, onUpdateScope:stage});
t1.progress(bg.position( Math.abs( trackpad.value ) / 3240));
I then tried the following - it didn't work (but I didn't get an error):
var moveIt = trackpad.value / 3240;
t1.progress(bg.position, moveIt, {});
Here's the code where the trackpad value is defined:
/*
* param: the html element that will be scrolled
*/
Trackpad = function(target)
{
this.target = target;
this.value = 0;
this.easingValue = 00;
this.dragOffset = 0;
this.dragging;
this.speed= 0;
this.prevPosition = 0;
$(this.target).mousedown($.proxy(this.onMouseDown, this));
this.target.onmousewheel = $.proxy(this.onMouseWheel, this);
// not forgetting touchs!
this.target.ontouchstart = $.proxy(this.onTouchStart, this);
// stop dragging!
$(document).keydown( $.proxy(this.onArrow, this))//function(e){
//this.target.ondragstart = function(){return false;}
}
// set constructor
Trackpad.constructor = Trackpad;
// create the functions
Trackpad.prototype.unlock = function()
{
this.locked = false;
this.speed = 0;
this.easingValue = this.value;
}
Trackpad.prototype.lock = function()
{
this.locked = true;
}
Trackpad.prototype.update = function()
{
if(this.easingValue > 0)this.easingValue = 0;
if(this.easingValue < -10700)this.easingValue = -10700;
this.value = this.easingValue;
if(this.dragging)
{
var newSpeed = this.easingValue - this.prevPosition;
newSpeed *= 0.7;
this.speed += (newSpeed - this.speed) *0.5;//+= (newSpeed - this.speed) * 0.5;
this.prevPosition = this.easingValue;
}
else
{
this.speed *= 0.9;
this.easingValue += this.speed;
if(Math.abs(this.speed) < 1)this.speed = 0;
}
}
Trackpad.prototype.onArrow = function(event)
{
if (event.keyCode == 38) {
// UP
this.speed = 4;
return false;
}
else if (event.keyCode == 40) {
// UP
this.speed -= 4
return false;
}
}
Trackpad.prototype.onMouseWheel = function(event)
{
event.preventDefault();
this.speed = event.wheelDelta * 0.1;
}
Trackpad.prototype.startDrag = function(newPosition)
{
if(this.locked)return;
this.dragging = true;
this.dragOffset = newPosition - this.value;
}
Trackpad.prototype.endDrag = function(newPosition)
{
if(this.locked)return;
this.dragging = false;
}
Trackpad.prototype.updateDrag = function(newPosition)
{
if(this.locked)return;
this.easingValue = (newPosition - this.dragOffset);
}
/*
* MOUSE
*/
Trackpad.prototype.onMouseDown = function(event)
{
if(event)event.preventDefault();
event.returnValue = false;
$(document).mousemove($.proxy(this.onMouseMove, this));
$(document).mouseup($.proxy(this.onMouseUp, this));
this.startDrag(event.pageY);
}
Trackpad.prototype.onMouseMove = function(event)
{
if(event)event.preventDefault();
this.updateDrag(event.pageY);
}
Trackpad.prototype.onMouseUp = function(event)
{
//$(this.target).mousemove(null);
$(document).unbind('mousemove');
$(document).unbind('mouseup');
//this.target.onmousemove = null;
this.endDrag();// = false;
}
/*
* TOUCH!
*/
Trackpad.prototype.onTouchStart = function(event)
{
//event.preventDefault();
this.target.ontouchmove = $.proxy(this.onTouchMove, this);
this.target.ontouchend = $.proxy(this.onTouchEnd, this);
this.startDrag(event.touches[0].clientY);
}
Trackpad.prototype.onTouchMove = function(event)
{
event.preventDefault();
this.updateDrag(event.touches[0].clientY);
}
Trackpad.prototype.onTouchEnd = function(event)
{
this.target.ontouchmove = null;
this.target.ontouchend = null;
this.endDrag();
}
** edit
tl = new TimelineLite( { paused: true } );
// respond to scroll event - in this case using jquery
$(window).scroll();
//apply whatever math makes the most sense to progress the timeline progress from 0 to 1 within those parameters. Something like,
$(window).scroll( function() {
var st = $(this).scrollTop();
if ( st < someArbitraryValue ) { // someArbitraryValue, where to start
// Here, "someOtherArbitaryValue" would be the
// "height" of the scroll to react to
tl.progress( Math.abs( st ) / someOtherArbitaryValue );
}
});
Is this the kind of effect you were after?
JavaScript:
window.requestAnimFrame=(function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(callback){window.setTimeout(callback,1000/60);};})(); //http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
var stageWidth=$(window).innerWidth();
var stageHeight=$(window).innerHeight();
var renderer=PIXI.autoDetectRenderer(stageWidth,stageHeight);
var bg,cat,moon,blue,trackpad,texture1,texture2,texture3;
document.body.appendChild(renderer.view);
texture1=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/StackOverflow/1.5/cat.jpg');
texture2=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/StackOverflow/1.5/moon.jpg');
texture3=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/StackOverflow/1.5/blue.jpg');
bg=new PIXI.Container();
cat=new PIXI.Sprite(texture1);
moon=new PIXI.Sprite(texture2);
blue=new PIXI.Sprite(texture3);
cat.anchor.x=cat.anchor.y=moon.anchor.x=moon.anchor.y=blue.anchor.x=blue.anchor.y=0;
cat.position.x=cat.position.y=moon.position.x=blue.position.x=bg.position.x=bg.position.y=0;
cat.width=moon.width=blue.width=stageWidth;
moon.position.y=1080;
blue.position.y=2160;
bg.addChild(cat);
bg.addChild(blue);
bg.addChild(moon);
bg.vy=bg.vx=0;//what are those?
trackpad=new Trackpad(document);
requestAnimFrame(animate);
function animate(){
requestAnimFrame(animate);
bg.position.y=trackpad.value;
trackpad.update();
renderer.render(bg);
}
Let me know if this is exactly the thing you were looking for & I'll then break it down for you in terms of what has changed in comparison to your code.
Notes:
First & foremost, I have used the latest version (v3.0.6) of Pixi.JS in my example above. This v3 update brought a few major changes. Couple of them prominent to your problem are:
No need for Stage object anymore for rendering purposes. Any Container type object can be used directly to be rendered on canvas.
Shortening of the name DisplayObjectContainer to simply Container. This is probably the reason why you are getting the error when trying to implement my code in your environment that you mentioned in comments because I presume you are using one of the old verions.
Read all about this update here, here & here.
I always prefer to use the latest & greatest of GSAP (v1.17.0). Even the dot releases of this framework brings major updates which is why I like to keep it up to date. Read an important note on this update here. Having said that, the current implementation doesn't really use TweenMax at all.
TweenMax bundles EasePack, CSSPlugin & a few other things. No need to load them in separately. Update your HTML accordingly. Use this handy GSAP CheatSheet by Peter Tichy to get such information and more about this tool.
Changes in Trackpad.js:
Inside the update method, there was a maximum scroll limit defined the page can scroll up to. That value previously was -10700. I changed it to -2160. You may want to set it to -3240 I think, based on what I have been able to understand so far as to what you are trying to achieve.
Formatting changes.
Changes in main.js (whatever name you gave to your main script file):
Added a requestAnimationFrame polyfill thanks to Paul Irish.
Removed the var stage= new PIXI.Stage(0xff00ff); line. Read #1 above for details.
Renamed DisplayObjectContainer to Container which was assigned to bg. Read #1 above for details.
Added bg.position.y=trackpad.value; in the animate loop. You were missing this. You will need to use trackpad.value in order to position your bg.
Added trackpad.update(); in the same animate loop. This is the big one and IMHO, this is the one you were failing to understand the purpose of. In summary, Trackpad.js needs to update its value on a timely basis & the only loop you have got running is the animate loop thanks to requestAnimFrame. Hence, the update(); method is called.
Rendering bg instead of stage. Read #1 above for details.
Formatting changes.
Let me know if anything is unclear.
T
I thought of editing the old answer but decided against it because I think it answers your original question.
Take a look at this Codepen demo for a new approach to the same problem. I am really hoping to listen to community on the approach I have taken here in terms of listening to events and using them to adjust a GSAP timeline.
There are 4 JS files used in my example: app.js, constants.js, timeline.js & listeners.js. Links to which can be found in the settings gear icon of the JavaScript editor of the demo. All of these files are heavily annotated with links to solutions I found over the internet to specific problems.
Among these files, code of app.js is as follows:
JavaScript:
function Application(){}
Application.prototype.init=function(){
this.constants=Constants.getInstance();
this.BASE_URL=this.constants.BASE_URL;
this.IMAGE_JS_URL=this.constants.IMAGE_JS_URL;
this.IMAGE_PIXI_URL=this.constants.IMAGE_PIXI_URL;
this.IMAGE_GSAP_URL=this.constants.IMAGE_GSAP_URL;
this.createPolyfillForBind();
this.setupRenderer();
this.loadImages();
};
Application.prototype.setupRenderer=function(){
this.stageWidth=window.innerWidth;
this.stageHeight=window.innerHeight;
//this.renderer=PIXI.autoDetectRenderer(this.stageWidth,this.stageHeight);
this.renderer=new PIXI.CanvasRenderer(this.stageWidth,this.stageHeight);
document.body.appendChild(this.renderer.view);
};
Application.prototype.loadImages=function(){
var self=this;
this.loader=new PIXI.loaders.Loader(this.BASE_URL,1,{crossOrigin:''}); // PIXI Loader class [http://pixijs.github.io/docs/PIXI.loaders.Loader.html]
this.loader.add(this.IMAGE_JS_URL); // Loader extends ResourceLoader [http://adireddy.github.io/docs/haxe-pixi/v3/types/pixi/plugins/resourceloader/ResourceLoader.html]
this.loader.add(this.IMAGE_PIXI_URL);
this.loader.add(this.IMAGE_GSAP_URL);
//this.loader.once('complete',function(){self.onImagesLoaded.apply(self);}); // Vanilla JS alternative to jQuery's proxy() method [http://stackoverflow.com/a/4986536]
this.loader.once('complete',this.onImagesLoaded.bind(this)); // bind() polyfill [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Polyfill]
this.loader.load();
};
Application.prototype.onImagesLoaded=function(){
this.setupSprites();
this.initTimeline();
this.initListeners();
this.startTicker();
};
Application.prototype.setupSprites=function(){
this.containerBg=new PIXI.Container();
this.spriteJS=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_JS_URL]); // TextureCache in action [http://www.html5gamedevs.com/topic/7674-load-textures-synchronously/?p=45836]
this.spritePIXI=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_PIXI_URL]); // PIXI.TextureCache became PIXI.utils.TextureCache in v3 [http://www.html5gamedevs.com/topic/14144-v3-utilstexturecache-utils-is-not-defined/?p=80524]
this.spriteGSAP=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_GSAP_URL]);
this.containerBg.addChild(this.spriteJS);
this.containerBg.addChild(this.spritePIXI);
this.containerBg.addChild(this.spriteGSAP);
this.spriteJS.anchor.x=this.spriteJS.anchor.y=this.spritePIXI.anchor.x=this.spritePIXI.anchor.y=this.spriteGSAP.anchor.x=this.spriteGSAP.anchor.y=0;
this.spriteJS.position.x=this.spriteJS.position.y=this.spritePIXI.position.x=this.spriteGSAP.position.x=this.containerBg.position.x=this.containerBg.position.y=0;
this.scaleImage(this.spriteJS);
this.scaleImage(this.spritePIXI);
this.scaleImage(this.spriteGSAP);
this.spritePIXI.alpha=this.spriteGSAP.alpha=0;
this.spriteJS.position.y=this.constants.GUTTER;
this.spritePIXI.position.y=this.spriteJS.height*2+this.constants.GUTTER;
this.spriteGSAP.position.y=this.spriteJS.height+this.spritePIXI.height*2+this.constants.GUTTER;
};
Application.prototype.scaleImage=function(sprite){
//var scale=Math.min(this.stageWidth/sprite.width,this.stageHeight/sprite.height); // resize with aspect ratio [http://community.createjs.com/discussions/createjs/547-resizing-canvas-and-its-content-proportionally-cross-platform#comment_27266530] and [https://opensourcehacker.com/2011/12/01/calculate-aspect-ratio-conserving-resize-for-images-in-javascript/]
var scale=this.stageWidth/sprite.width;
sprite.scale.x=sprite.scale.y=scale;
};
Application.prototype.initTimeline=function(){
this.timeline=new Timeline();
this.timeline.init(this.containerBg,this.spriteJS,this.spritePIXI,this.spriteGSAP,this.stageWidth,this.stageHeight);
};
Application.prototype.initListeners=function(){
var self=this;
//this.listeners=new Listeners();
//this.constants.setListenersObject(this.listeners);
//this.listeners.init();
this.listeners=Listeners.getInstance();
this.listeners.addListeners();
document.addEventListener(this.constants.SCROLLED,this.onScroll.bind(this),false);
document.addEventListener(this.constants.STARTED_DRAG,this.onStartDrag.bind(this),false);
document.addEventListener(this.constants.DRAGGED,this.onDrag.bind(this),false);
document.addEventListener(this.constants.END_DRAG,this.onEndDrag.bind(this),false);
};
Application.prototype.onScroll=function(e){ this.timeline.onScroll(e); };
Application.prototype.onStartDrag=function(e){ this.timeline.onStartDrag(e); };
Application.prototype.onDrag=function(e){ this.timeline.onDrag(e); };
Application.prototype.onEndDrag=function(e){ this.timeline.onEndDrag(e); };
Application.prototype.startTicker=function(){
var self=this;
//TweenLite.ticker.addEventListener('tick',function(){self.render.apply(self);},false); // Vanilla JS alternative to jQuery's proxy() method [http://stackoverflow.com/a/4986536]
TweenLite.ticker.addEventListener('tick',this.render.bind(this),false);
};
Application.prototype.render=function(){this.renderer.render(this.containerBg);};
Application.prototype.createPolyfillForBind=function(){ // [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Polyfill]
if(!Function.prototype.bind){
Function.prototype.bind=function(oThis){
if(typeof this!=='function'){
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs=Array.prototype.slice.call(arguments,1),
fToBind=this,
fNOP=function(){},
fBound=function(){
return fToBind.apply(this instanceof fNOP
?this
:oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype=this.prototype;
fBound.prototype=new fNOP();
return fBound;
};
}
};
//
var app=new Application();
app.init();
P.S. I have also heavily experimented with design patterns in this same example, mainly Prototype and Singleton patterns. I am also looking forward to comments on them as well from the community.
T
Currently programming Hangman and I have the following problem:
When I create a new HangmanController (choosing a new word, ...) the timer works fine, the only thing is, when I create one more HangmanController, the speed is 2x faster, when I create one more HangmanController 3x, etc.
I create a HangmanController only when
Where's the problem? Or how can I make the timer better?
//Here's where I create a new HangmanController. This happens when the user changes the difficulty (pressing a button in the HTML)
function inithm(difficulty) {
hm = new HangmanController();
hm.hmModel.reset(hm.displayedWord.word, false, difficulty);
UsedLetters();
};
//Constructor
HangmanController = function() {
this.hmModel = new HangmanModel();
//this.hmView = new HangmanView(document.getElementById('hm_view'));
this.displayedWord = new DisplayedWord();
this.userGuessField = document.getElementById('guessfield');
this.userGuessField.focus();
this.guessbutton = document.getElementById('guessbutton');
this.guessbutton.disabled = false;
/*this.updateTime = function(stopwatch) {
var e = stopwatch.getElapsed();
document.getElementById('stopwatch').innerHTML = e.hours * 60 + e.minutes + ' mins' + e.seconds + ' secs';
};*/
this.stopwatch = new Stopwatch();
};
//Timer, it is only called here, nowhere else (at setInterval)
Stopwatch = function() {
this.sek = 0;
setInterval(function() {timer();}, 1000);
};
function timer() {
document.getElementById('stopwatch').innerHTML = this.hm.stopwatch.sek;
this.hm.stopwatch.sek++;
}
Stopwatch.prototype.stop = function() {
document.getElementById('stopwatch').innerHTML = 0;
};
You are creating several instances of Stopwatch with each HangmanController, and everyone of them is calling timer() every second, which results in your #stopwatch element beeing increased more often than just once a second ;-)
edit: You should either dispose of the old HangmanController (especially the Stopwatch functionality) before creating a new one, or instead only create one instance of HangmanController, and make a method for restarting, or difficulty changes, etc..
I apologize for the amount of code, but I think this is actually a problem with AppMobi's getCurrentLocation function. Basically what happens is I delegate a tap event to each list element. Then when you tap it, it runs an asynchronous getCurrentLocation and updates some stuff. Then the next time you tap another list element, the variables bound in the callback of the getCurrentLocation Function only refer to the first time it was called. Why doesn't this work??
app = { events: [{text: "foo", time: new Date()}, {text: "bar", time: new Date()}] };
$(document).ready(refreshEvents);
function refreshEvents() {
for (var index in app.events) {
insertEventHTML(app.events[index]);
}
}
function insertEventHTML(event) {
var text = event.text;
var time = event.time;
var new_element = $('<li class="event_element"></li>');
var new_checkin_element = $('<div class="check_in_button"></div>');
new_checkin_element.bind('tap', function(e) {
check_in(e);
fade($(this), 1.0);
});
new_element.append(new_checkin_element);
var text_element = $('<div class="text_element">' + text + '</div>');
new_element.append(text_element);
var time_element = $('<div class="time_element">' + time + '</div>');
new_element.append(time_element);
$('#your_events').append(new_element);
}
function check_in(e) {
$(e.target).siblings('.time_element').text('just now');
var time = new Date(); // time and event_index are the trouble variables here
var event_index = getEventIndex(e); // the first time this function runs, event_index is correct
// then each subsequent time, it remains the same value as the first
if (!app.settings.use_location) {
app.events[event_index].times.unshift({time: time, location: null});
} else {
AppMobi.geolocation.getCurrentPosition(onLocationFound, errorFunction);
}
function onLocationFound(response) {
var lat = response.coords.latitude;
var lon = response.coords.longitude;
var last_time = app.events[event_index].times[0];
if (last_time != undefined && last_time.time == time) {
// event_index and time here will only ever refer to the first time it was called. WHY???
add_checkin(response, event_index, time);
}else{
console.log('onLocationFound was called twice');
}
}
function errorFunction(error) {
$.ui.popup({title: 'geolocation error', message: 'Geolocation error. Turn off location services in settings.'});
}
function add_checkin(response, event_index, time) {
// and then of course event_index and time are wrong here as well. I don't get it.
app.events[event_index].times.unshift(
{
time: time,
location: {
latitude: response.coords.latitude,
longitude: response.coords.longitude
}
});
AppMobi.cache.setCookie('app', JSON.stringify(app), -1);
}
}
function getEventIndex(e) {
var target = $(e.target).parent();
var siblings = target.parent().children('li');
for (var i = 0; i < siblings.length; i++) {
if ($(target)[0].offsetTop == $(siblings[i])[0].offsetTop) {
return i;
}
}
}
Well, your issue seems to be that you are declaring a private variable event_index inside the check_in function and try to resolve it's value by accessing a global event_index variable inside onLocationFound.
Here's what you could do instead:
AppMobi.geolocation.getCurrentPosition(function (response) {
onLocationFound(response, event_index);
}, errorFunction);
function onLocationFound(response, event_index) { //... }
EDIT:
it is declared within check_in...
You are right, I totally missed that somehow. Well in that case it's very unlikely that the event_index variable inside onLocationFound isin't the same as in check_in. Do a console.log(event_index) inside onLocationFound and it should be the same. The only way it could be different is if you modify the local variable before the handler is called, which you doesn't seem to do, or if getCurrentPosition stores the first handler somehow and ignores subsequent handlers, but this API wouldn't make any sense.
EDIT 2:
As we suspect the handler might not be registered correctly the second time, I would suggest to check this way:
function check_in() {
if (!check_in.called) {
check_in.called = true;
check_in.onLocationFound = onLocationFound;
}
//...
function onLocationFound() {
console.log(arguments.callee === check_in.onLocationFound);
}
}
You can also simple do onLocationFound.version = new Date() and check arguments.callee.version to see if it stays the same.
I know you marked this answered already but....it might not actually be a problem with the library. Can I direct you to a post by #Joel Anair where he has posted an article and the example number five seems to be the "gotcha" which might have gotcha ;)
How do JavaScript closures work?
Basically in the for loop they are all being set to the same reference of i so event_index will all be the same value/reference. (which explains why they're all the same).
I have a timer function I got off this site and have tried to modify it a bit to suit my needs. For the most part it works as I'd like but I'm not able to pass a parameter to the JavaScript function.
I want to use a select to change the id value, then pass that value into the JQuery load function. Can someone please show me how to do this?
It may be worth mentioning that when the the timer refreshes, the select value needs to "stick", it can't reset.
Here is what I have so far:
function RecurringTimer(callback, delay) {
var timerId, start, remaining = delay;
this.pause = function() {
window.clearTimeout(timerId);
remaining -= new Date() - start;
};
var resume = function() {
start = new Date();
timerId = window.setTimeout(function() {
remaining = delay;
resume();
callback();
},remaining);
};
this.resume = resume;
this.resume();
}
var timer = new RecurringTimer(function() {
$('#id').load('load.asp?id=1'); // The "id" should be "newVal" from JQuery below.
}, 5000);
$(document).ready(function(){
$('#appList').live('change',function(){
var select = $(this);
var newVal = select.val();
if(newVal != ''){
// This is where the var newVal needs to be passed into the JavaScript.
}
});
});
I've speculated to a degree here what you're up to, so there were some changes I made to accommodate just trying to make it work. So if you have any questions about why I did something, just let me know.
Here's my mock elements, trying to match what you had in your code:
<div id="selector">
<select id="appList">
<option value="1">Application List - App 1</option>
<option value="2">Application List - App 2</option>
<option value="3">Application List - App 3</option>
<option value="4">Application List - App 4</option>
</select>
</div>
<div id="id"></div>
Note, #id is not a good identifier for an element; I almost renamed it to #loader several times. You might consider making this id attribute for this element more descriptive.
I did change the $.live to $.on; if the element you're working with in reality is like a select or something with a genuine change event, stick with $.on instead of $.live. $.live is deprecated and it probably won't be too many more versions before it's removed altogether.
I also switched out $.load for $.get so I could use the callback; jsFiddle doesn't really give me the option of working with $.load and returning anything. So I used $.get and substituted a log statement instead.
Ok, so what you'll see is a wrapping jQuery(); around all the RecurringTimer() function, as well as the callback, timer, and $.on. What this does is create a "closure scope" (think like it's enclosing that code it contains), so I'm no longer in window or global scope. I can access window by calling window, but this will belong to jQuery.ready(), which implies document.
Right at the top you'll see this:
jQuery(function run($){
var $id = $('#id'),
$applist = $('#appList'),
timerId,
start,
remaining = 0,
newval,
select;
These are all private variables that will stay in scope for the function and callbacks that belong to this scope. So we can share them if I have them at this "top most private" scope. If they are called with var inside of the other functions in this scope, they will only belong to that scope. Like var select = $(this); is only accessible within that $.on handler.
Hence, I want var newval; at that top most private scope, so we can easily share it. You have to be careful, of course, since there are times you don't want to shove it all up to that scope (like var select).
Probably the rest of it is self-explanatory. You don't need window.setTimeout, just setTimeout. Most window-scoped properties and methods don't require you to prepend window. However, you may need to do so to disambiguate a local and global/window property, for instance if you had a local var location = 'http://...'; in scope. You would need window.location to get that in that case.
Let me know if you have any questions.
jQuery(function run($){
var $id = $('#id'),
$applist = $('#appList'),
timerId,
start,
remaining = 0,
newval,
select;
$applist.on('change', function(){
var select = $(this);
$id.prepend('<p>App List change event fired</p>');
newval = select.val();
if (newval != '') {
var timer = new RecurringTimer(function() {
$.get('/echo/html/?id=' + newval, callback);
}, 5000);
}
function callback(msg, status){
var report = '<p>Callback returned ' + status +
' for ' + this.url + '.</p>';
$id.prepend(report);
}
});
function RecurringTimer(callback, delay) {
remaining = delay;
$id.prepend('<p>RecurringTimer initialized.</p>');
this.pause = function() {
clearTimeout(timerId);
remaining -= new Date() - start;
};
var resume = function() {
start = new Date();
timerId = setTimeout(function() {
remaining = delay;
resume();
callback();
}, remaining);
};
this.resume = resume;
this.resume();
}
});
http://jsfiddle.net/userdude/zU5b3/
There's also some CSS, but I don't think it's relevant, so I left it out.
Well, you could do:
if(newVal != ''){
window.newVal = newVal;
}
And use window.newVal where needed:
$('#id').load('load.asp?id=' + window.newVal);
Try this
function RecurringTimer(callback, delay) {
var timerId, start, remaining = delay;
var newValue;
.....
.....
.....
and then in your jquery
if(newVal != ''){
newValue = newVal;
}
Then you can reference it inside your timer
var timer = new RecurringTimer(function() {
$('#id').load('load.asp?id='+newValue); // The "id" should be "newVal" from JQuery below.
}, 5000);