This question already has answers here:
How to avoid global variables in JavaScript?
(13 answers)
Closed 9 years ago.
Im kind of a newbie on javascript still, I would say, so I think that the ways I work on projects now are kind of shaping my way of thinking about how things should be done in js. I have understood that one should work preferably with modules when programming in js. This has led me to think about events in js... For example say I have this object Monkey
function Monkey(color, position){
this.color = color;
this.position = position;
this.jumpAround = function() {
/* jumps around and screams */
};
}
And say I have built a whole app like this, with monkeys, giraffes and shih tzus, that interact in a webapp. How should the events then be handled? Are you left to just implement callbackfunctions in a global namespace? Like:
//objects
var monkey = new Monkey(brown, pos);
var giraffe = new Giraffe(yellow, pos);
var shih_tzu = new Shih_tzu(white, pos);
//event handlers
this_and_that.onclick = function() { /* this and that happens */ }
...
And then include this file in the html header? Maybe this is a silly question with an obvious answer, but still too me it seems as if there aren´t any good best practices...
You can put all your code within anonymous self-invoking function, this will also create closure:
(function(){
// create all your objects and define all events handlers here
})();
Then your code will not pollute global namespace and will be inaccessible from outside. All your event handlers will be executed within the closure.
(One side note: you can find this also in jQuery library. At the very end of the script file is the jQuery object exposed to outer world: window.jQuery = window.$ = jQuery;)
Not entirely sure I understand the question, but if you mean handling the overwriting of events in javascript caused by repeat elem.onclick = function() {} I use this function:
function addEvent(obj,event,func)
{
if(typeof func !== 'function')
{
return false;
}
if(typeof obj.addEventListener == 'function' || typeof obj.addEventListener == 'object')
{
return obj.addEventListener(event.replace(/^on/,''), func, false);
}
else if(typeof obj.attachEvent == 'function' || typeof obj.attachEvent == 'object')
{
return obj.attachEvent(event,func);
}
}
addEvent(this_and_that,'onclick',function(e) {
//do stuff
});
Here's a fleshed out example for you with object inheritance. http://jsfiddle.net/H4jqF/
CSS - just a base animal object, with a .5 second smooth transition, for when properties change (for example to make our animal JUMP).
.animal {
position: absolute;
transition: all 0.5s ease;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
}
JavaScript
// simple HTML animal - parent class
function Animal(color, position) {
this.color = color;
this.position = position;
this.elm = document.createElement("IMG");
this.elm.className = "animal";
this.elm.style.backgroundColor = this.color;
this.update = function() {
// update the the HTML element
this.elm.style.left = this.position.x + "px";
this.elm.style.top = this.position.y + "px";
};
this.jump = function(height) {
// cheesy jump animation
// we'll use a CSS transition to smooth it out
this.position.y -= height;
this.update();
// hard code it to come back down in 500ms
// .bind(this) is used to scope the function to this instance
window.setTimeout(function() {
this.position.y += height;
this.update();
}.bind(this), 500);
};
this.update();
}
// our subclass Dog
function Dog(color, position) {
// inherit all of the parent objects properties and methods
Animal.apply(this, arguments);
// finish setup of the element
this.elm.src = "dog.png";
document.body.appendChild(this.elm);
// add our onclick event that will fire jump
this.elm.onclick = function() {
this.jump(100);
}.bind(this);
}
// spawn some dogs
new Dog("red", {x: 50, y: 200});
new Dog("blue", {x: 200, y: 200});
Related
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
So i'm trying to make a "game" with javascipt witc has rectangles that go arround the canvas and they have timers. When rectangles timer goes above certain limit rectangle gets a new direction.
I'm trying to make the rectangles with class so i can have multiple of them running at same time. But the problem is that i cant get the functions inside of the class pass data to eachother (i think that's the problem...).
On the draw function this.cubeLocX is not defined and this.ctx.fillStyle="#2a2b2d"; gets error Cannot set property 'fillStyle' of undefined
class cubeV3 {
constructor(canvas) {
//var canvas = document.getElementById('canvas');
this.canvas = canvas;
this.cubeLocX = 100;
this.cubeLocY = 0;
this.speed = getRandomNumber();
this.dir = 0;
this.turnTimer = 0;
this.turnTimerMax = getRandomNumberV2(30,100);
this.ctx = this.canvas.getContext("2d");
}
testClass() {
alert('This is CubeV3 classes testfunction reporting in!');
/*you have put the call to the private method inside the constructor of the javascript class. in that point the functions are not yet initialized
but if you initialize the object like so:
var test = new MyObject();
and then do this:
test.myMethod();
it will work.*/
}
update() {
this.turnTimer ++;
if (this.turnTimer > this.turnTimerMax) {
this.dir = this.turnTimerMax = getRandomNumberV2(1,4);
this.turnTimer = 0;
this.turnTimerMax = getRandomNumberV2(30,100);
}
if (this.dir == 1) {this.cubeLocY -=this.speed;}//UP
if (this.dir == 2) {this.cubeLocY +=this.speed;}//DOWN
if (this.dir == 3) {this.cubeLocX -=this.speed;}//LEFT
if (this.dir == 4) {this.cubeLocX +=this.speed;}//RIGHT
}
draw(self,) {
//this.cubeLocX = 555;
resetCanvas();
console.log(this.cubeLocX);
alert(this.cubeLocX);
this.ctx.fillStyle="#2a2b2d";
this.ctx.fillRect(this.cube_loc_x,this.cube_loc_y,25,25);
}
}
So how can i make my class work?
EDIT calling the class
This is placed after the draw for calling and updating the class
//setInterval(resetCanvas,10)
var testCube = new cubeV3(canvas);
setInterval(cube, 10);
//setInterval(testCube, 10);
setInterval(testCube.update, 10);
setInterval(testCube.draw, 10);
Advice 1: "this" can be tricky in js, avoid it if it is possible.
Advice 2: maybe it is not the best Oop design to define the draw function in the rectangle class. It should be better to define a standalone draw function which has a shape argument:
function draw(shape){
shape.ctx.fillStyle = ...
...
}
This way you can avoid some headache in the future also.
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
I'm trying to understand jQuery classes but it is not going very well.
My goal is to use a class this way (or to learn a better way to do it):
var player = new Player($("playerElement"));
player.InitEvents();
Using other people's examples, this is what I tried:
$.Player = function ($) {
};
$.Player.prototype.InitEvents = function () {
$(this).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
The end goal is to have a character moving on the screen left and right using the keyboard letters A and D.
I have a feeling that I'm doing something very wrong with this "class"
but I'm not sure why.
(sorry for my English)
An important issue is that you have to assign the passed jQuery object/element to a this.element - or another this.propertyName - so you can access it later inside the instance's methods.
You also cannot call MoveRight()/MoveLeft() directly like that because those functions are not defined up in the scope chain, but rather in the prototype of your instance's Constructor, hence you need a reference to the instance itself to call these.
Updated and commented code below:
(function ($) { //an IIFE so safely alias jQuery to $
$.Player = function (element) { //renamed arg for readability
//stores the passed element as a property of the created instance.
//This way we can access it later
this.element = (element instanceof $) ? element : $(element);
//instanceof is an extremely simple method to handle passed jQuery objects,
//DOM elements and selector strings.
//This one doesn't check if the passed element is valid
//nor if a passed selector string matches any elements.
};
//assigning an object literal to the prototype is a shorter syntax
//than assigning one property at a time
$.Player.prototype = {
InitEvents: function () {
//`this` references the instance object inside of an instace's method,
//however `this` is set to reference a DOM element inside jQuery event
//handler functions' scope. So we take advantage of JS's lexical scope
//and assign the `this` reference to another variable that we can access
//inside the jQuery handlers
var that = this;
//I'm using `document` instead of `this` so it will catch arrow keys
//on the whole document and not just when the element is focused.
//Also, Firefox doesn't fire the keypress event for non-printable
//characters so we use a keydown handler
$(document).keydown(function (e) {
var key = e.which;
if (key == 39) {
that.moveRight();
} else if (key == 37) {
that.moveLeft();
}
});
this.element.css({
//either absolute or relative position is necessary
//for the `left` property to have effect
position: 'absolute',
left: $.Player.defaultOptions.playerX
});
},
//renamed your method to start with lowercase, convention is to use
//Capitalized names for instanceables only
moveRight: function () {
this.element.css("left", '+=' + 10);
},
moveLeft: function () {
this.element.css("left", '-=' + 10);
}
};
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
}(jQuery));
//so you can use it as:
var player = new $.Player($("#playerElement"));
player.InitEvents();
Fiddle
Also note that JavaScript does not have actual "classes" (at least not until ES6 gets implemented) nor Methods (which by definition are associated exclusively to Classes), but rather Constructors which provide a sweet syntax that resembles classes. Here's an awesome article written by TJ Crowder regarding JS's "fake" methods, it is a little advanced but everyone should be able to learn something new from reading it:
http://blog.niftysnippets.org/2008/03/mythical-methods.html
When you use this inside your Player prototype functions, this points to the current Player object.
But when you use $(this).keypress it requires that this points to an HTML element.
The two simply are incompatible. There is only one this and it points to the current Player object, not to an HTML element.
To fix your problem, you will need to pass the HTML element into the Player object upon its creation or into the relevant function calls.
You can pass the element into the Player object upon construction like this:
$.Player = function ($, element) {
this.element = element;
};
$.Player.prototype.InitEvents = function () {
$(this.element).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this.element).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this.element).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
I'm just trying to structure my Javascript better and wondering how to incorporate window.onresize into the returned object, like so:
var baseline = function(){
var tall, newHeight, target, imgl, cur, images = [];
return {
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
window.onresize = this.setbase(this.images);
},
setbase: function(imgs){
this.imgl = imgs.length;
if(this.imgl !== 0){
while(this.imgl--){
this.cur = imgs[this.imgl];
this.cur.removeAttribute("style");
this.tall = this.cur.offsetHeight;
this.newHeight = Math.floor(this.tall / this.target) * this.target;
this.cur.style.maxHeight = this.newHeight + 'px';
}
} else {
return false;
}
}
}
}();
Is this the way that people would do it, is this going to work? Thanks
EDIT:
Invoked like so:
window.onload = function(){
baseline.init('img', '24');
};
I would like it so that when the window is resized, baseline.init is called with the same params as the initial init function call...
Here's the main error
init: function(selector, target){
this.images = document.querySelectorAll(selector);
this.target = target;
this.setbase(this.images);
// This line says call setbase now and assign the result of that
// as the onresize handler
window.onresize = this.setbase(this.images);
},
Your this.images does not point to the var images = [] you've created. This is for when you're using protoype style objects. You should just use images in your functions.
Some of your variables look like they're only used in setBase, they should be local
Looking at your object, it's very hard to tell what it's supposed to do, sounds like you're wrapping code in an object just for the sake of wrapping it into an object. What does baseline mean?
Here's a better version of your code, you should read and understand http://www.joezimjs.com/javascript/javascript-closures-and-the-module-pattern/ and http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html so you can decide what pattern you want to use and how they actually work. You are mixing both patterns, even though you didn't intend to. The trick is that with the way you're writing it (module pattern) there's no need to use this in the code, they're actually local variables held be the module
var baseline = function(){
// Don't use "this.tall", just "tall" gets you the variable
// Class variables, are you sure you need them throughout the class
var tall, newHeight, target, imgl, cur, images = [];
// Different name for the parameter so it doesn't get confused with
// the class variables
function init(selector, pTarget) {
images = document.querySelectorAll(selector);
target = pTarget;
setBase();
// Since we're not using this, you
// can just reference the function itself
window.onresize = setBase
}
// Most JS developers name methods using camelCase
function setBase() {
imgl = imgs.length;
if(imgl !== 0){
while(imgl--){
cur = imgs[imgl];
cur.removeAttribute("style");
tall = cur.offsetHeight;
newHeight = Math.floor(tall / target) * target;
cur.style.maxHeight = newHeight + 'px';
}
// should you return true here? what does returning
// something even mean here?
} else {
return false;
}
}
// Return just the public interface
return {
init: init
setBase: setBase
};
}();
Hey I was wondering how I could add on to my function draw(); draw is used in my canvas engine to update everything within it. What I want to do is create a standalone engine that could say be left un-edited and yet update completely new things just linked to it. For example-
function draw(){
gameloop();
cameraWrapper();
context2D.clearRect(0,0,canvas.width, canvas.height);
}
Now say I create a new app and use this engine.. I want to be able to just create a standalone file linked to the engine say a player object.
player = new object();
function playerupdate(){
stuff;
stuff;
}
Now how could I say add the playerupdate() function into the engine.js's draw() function without editing the engine.js file? would this be like a prototype? if so and even if its not and example would be greatly appreciated! If you have any questions please ask, thanks in advance!
I think inheritance is a bit too complex for this... you can achieve all of what you want with just hooks.
Try something like this:
var postDrawHooks = [];
var draw = function(){
// do stuff
postDrawHooks.forEach(function(hook){hook()});
}
var playerUpdate = function(){...};
postDrawHooks.push(playerUpdate);
Basicaly it is a prototype. If you do not wish to complicate yourself with prototypeing you can use a "home made" inheritance:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
Function.method('inherits', function(parent) {
var d = {}, p = (this.prototype = new parent());
this.method('uber', function uber(name) {
if(!( name in d)) {
d[name] = 0;
}
var f, r, t = d[name], v = parent.prototype;
if(t) {
while(t) {
v = v.constructor.prototype;
t -= 1;
}
f = v[name];
} else {
f = p[name];
if(f == this[name]) {
f = v[name];
}
}
d[name] += 1;
r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
d[name] -= 1;
return r;
});
return this;
});
Now for a "class" (there is no such thing in js but this is the correct term ) you can make it inherit another "class" by using myCls.inherits(parentCls)
is there a reason you couldn't trigger events for these framework actions? that way anything listening for a 'draw' event could just hook their logic in that way? if not actual eventing, something like the hooks suggested by #sudhir jonathan would work, though i would suggest creating a method to register generic hooks, this way you could call something like
game.register('draw',myFunctionReference);
and in game object
register : function (hook,func) {
registeredHooks.push({'name' : hook, 'callback': func});
}
and in draw:
function draw(){
gameloop();
cameraWrapper();
context2D.clearRect(0,0,canvas.width, canvas.height);
for (i=0; i < registeredHooks.length; i++) {
var hook = registeredHooks[i];
if (hook.name == 'draw') hook.callback();
}
}