jQuery extend, cannot read property of undefined - javascript

I have this code:
var viewport = $(window),
viewport_height = viewport.height();
var _topPanel = jQuery.extend({
el: $('.js-lp_top'),
//
height: function() {
var self = this;
self.el.css('min-height', viewport_height);
},
scroll : function() {
var self = this;
var scrolled = viewport.scrollTop();
viewport.on('scroll', function() {
self.el.css({
'top': (49 - (scrolled / viewport_height) * 80) + '%'
});
});
}
});
var topPanel = new _topPanel.height().scroll();
And an jQuery error that Cannot read property 'css' of undefined. What i'm doing wrong? Thx for help.

Let's first examine this line of code.
var topPanel = new _topPanel.height().scroll();
The keyword new creates a new empty object. Inside the height function, the this keyword refers to this new object, which of course doesn't have an el property. self.el is undefined, hence the error message Cannot read property 'css' of undefined
There are two changes to make here:
Ensure your height and scroll functions returns this, to support function chaining
Don't include the new keyword when invoking the height function
Here's the modified code:
var _topPanel = jQuery.extend({
el: $('.js-lp_top'),
//
height: function () {
var self = this;
self.el.css('min-height', viewport_height);
return self;
},
scroll: function () {
var self = this;
var scrolled = viewport.scrollTop();
viewport.on('scroll', function () {
self.el.css({
'top': (49 - (scrolled / viewport_height) * 80) + '%'
});
});
return self;
}
});
var topPanel = _topPanel.height().scroll();

Related

Javascript / Jquery OOP not inheriting attributes

I have created a Constructor class / function that has 4 methods and a 5 attributes. The problem is when i create a new instance of the constructor it doesn't inherit the first attribute (this.el = element) which is 'affiliateSection' on the new instance called: animation1. Thanks in advance..
// main class
function AnimateImages(el, time, effect, setTime, h){
this.el = element; // this is not inheriting from 'animate1'
this.time = time;
this.effect = effect;
this.setTime = setTime;
this.h = height;
this.delayAnimate = function() {
this.element.delay(time)
.queue(function(next) {
$(this).addClass(effect);
next();
});
};
// function for multi animations
var multiAnimations = function() {
var i = 0;
this.element.each(function (key, value) {
i = i + setTime;
var tthis = this;
delayAnimate($(this), i, tthis.effect);
});
};
// load on window height
var onWindowAnimate = function () {
if ($(this).scrollTop() > this.height) {
// call multi animations function
var tthis = this;
multiAnimations(this.element, tthis.setTime, tthis.effect);
}
};
// hide function
var hideAnimatedEl = function (){
this.element.each(function(){
$(this).css("visibility", "hidden");
});
};
} // End of AnimateImages
/*============================================================*/
var affiliateSection = $("#aff-img > li > img");
// new instance of AnimateImages
var animation1 = new AnimateImages(affiliateSection, 200, 'subtlefadeIn',
300, 50);
$(window).scroll(function () {
setTimeout(function(){
animation1.onWindowAnimate();
}, 1000);
});
It looks like you have your member variable initializations backwards. Try this:
this.element = el; // this is not inheriting from 'animate1'
this.time = time;
this.effect = effect;
this.setTime = setTime;
this.height = h;
Your parameter name is wrong:
this.el = element;
element is not in the parameter list.
Since you are reffering to this.element inside your function, I am assuming that your first line should be
this.element = el;

Passing two different arguments to function

I have written some jQuery functions, and recently realized I needed to reuse the code for another situation. I refactored the code to accept a selector as an arguement, so I can now use for case 1, and case 2. However, when I execute my functions in document.ready I get weird results.
$( document ).ready(function() {
imageCalc('.com-background > img');
setImageDims('.com-background > img', '#main-content');
imageCalc('.blog-entry-content iframe');
setImageDims('.blog-entry-content iframe', '#content');
});
It should be noted, these selectors do no show up on the same page. Also, when I only run one instance of imageCalc() and setImageDims() These functions work just fine. Here are the functions in question..
function imageCalc(selector) {
var obj=$(selector);
$imgWidth = obj.width();
$imgHeight = obj.height();
$imgAspectRatio = $imgHeight / $imgWidth;
// $(selector).css('margin-left', function( calcMargin ) { return parseInt($('.main-content').css('padding')) * -1 + "px"; }); fix for ie
obj.css('margin-left', '-10px' );
}
function setImageDims(selector, content_area) {
var container = $(content_area);
$(selector).css('height', function() { return $imgAspectRatio * container.width(); });
$(selector).css('width', function() { return container.width() + 20; });
}
In summary, all the code works just fine, when I only have each function called only ONCE in document.ready but I need to use this code for 2 scenarios, how can I do this?
Add a var in front of your $imgWidth, $imgHeight, and $imgAspectRatio variables. Without the var, they're being declared at global scope, and therefore accidentally getting shared across both calls to that function.
UPDATE: I just noticed that the $imgAspectRatio is being used by both functions. Perhaps you can make that the return value from the first function, so it can be passed into the second function.
To elaborate... something like this should theoretically work, although I'm not able to test it since I don't have the corresponding HTML:
function imageCalc(selector) {
var obj=$(selector);
var $imgWidth = obj.width();
var $imgHeight = obj.height();
var $imgAspectRatio = $imgHeight / $imgWidth;
// $(selector).css('margin-left', function( calcMargin ) { return parseInt($('.main-content').css('padding')) * -1 + "px"; }); fix for ie
obj.css('margin-left', '-10px' );
return $imgAspectRatio;
}
function setImageDims(selector, content_area, $imgAspectRatio) {
var container = $(content_area);
$(selector).css('height', function() { return $imgAspectRatio * container.width(); });
$(selector).css('width', function() { return container.width() + 20; });
}
$( document ).ready(function() {
var ratio1 = imageCalc('.com-background > img');
setImageDims('.com-background > img', '#main-content', ratio1);
var ratio2 = imageCalc('.blog-entry-content iframe');
setImageDims('.blog-entry-content iframe', '#content', ratio2);
});
This will require you to re-work your functionsas setImageDims depends on $imgAspectRatio to be available globally.
function imageCalc(selector) {
var obj=$(selector),
$imgWidth = obj.width(),
$imgHeight = obj.height(),
$imgAspectRatio = $imgHeight / $imgWidth;
// $(selector).css('margin-left', function( calcMargin ) { return parseInt($('.main-content').css('padding')) * -1 + "px"; }); fix for ie
obj.css('margin-left', '-10px' );
return $imgAspectRatio;
}
function setImageDims(selector, content_area) {
var container = $(content_area);
$(selector).css('height', function() { return imageCalc(selector) * container.width(); });
$(selector).css('width', function() { return container.width() + 20; });
}

jQuery .data() is being overwritten

I currently have the following code for a jQuery tooltip plugin I am writing. I am storing the config for each tooltip with jQuery's .data() method. However, when I go to retrieve the data it has been overwritten by the most recently stored data from a completely different selector. I can't figure out the issue as it was working prior and then suddenly stopped. The main areas to look in are the addTooltip(), removeTooltip(), and displayTooltip().
Example:
$('#tooltip1').addTooltip('tooltip', { 'direction': 'bottom' });
$('#tooltip2').addTooltip('tooltip', { 'direction': 'left' });
In the above example I am selecting two completely different elements however when I display the #tooltip1 tooltip it will be using #tooltip2's config which in this case is 'direction': 'left'.
Any help is appreciated.
(function($) {
// Used as a template for addTooltip()
var tooltipDefaults = {
'class': null,
'showOn': 'mouseenter',
'hideOn': 'mouseleave',
'direction': 'top',
'offset': 0
}
// Store generated IDs to avoid conflicting IDs
var tooltipIds = new Array();
// Keep track of displayed popups
var displayedTooltips = new Array();
function generateUniqueId()
{
var id;
do {
id = Math.floor(Math.random()*90000) + 10000;
} while ($.inArray(id, tooltipIds) !== -1);
tooltipIds.push(id);
return id;
}
function getUniqueId(id)
{
return parseInt(id.substr(0, 5));
}
function isUniqueId(id)
{
return !NaN(getUniqueId(id));
}
function removeUniqueId(id)
{
var id = getUniqueId(id);
var idIndex = $.inArray(id, tooltipIds);
if (idIndex !== -1) {
tooltipIds.splice(idIndex);
}
}
$.fn.displayTooltip = function()
{
var element = $(this);
var tooltip = $('#' + element.attr('data-tooltip-id'));
var config = element.data('config');
var offset = element.offset();
var left;
var top;
switch (config.direction) {
case 'left':
top = offset.top + "px";
left = offset.left - tooltip.outerWidth() - config.offset + "px";
break;
case 'top':
top = offset.top - element.outerHeight() - config.offset + "px";
left = offset.left + ((element.outerWidth() / 2) - (tooltip.outerWidth() / 2)) + "px";
break;
case 'right':
top = offset.top + "px";
left = offset.left + element.outerWidth() + config.offset + "px";
break;
case 'bottom':
top = offset.top + element.outerHeight() + config.offset + "px";
left = offset.left + ((element.outerWidth() / 2) - (tooltip.outerWidth() / 2)) + "px";
break;
}
tooltip.css({
'position': 'absolute',
'left': left,
'top': top,
'z-index': 5000
});
if (element.isTooltipDisplayed()) {
return;
}
tooltip.show();
displayedTooltips.push(element.attr('id'));
}
$.fn.hideTooltip = function()
{
var element = $(this);
var idIndex = $.inArray(element.attr('id'), displayedTooltips);
if (idIndex !== -1) {
displayedTooltips.splice(idIndex);
}
$('#' + element.attr('data-tooltip-id')).hide();
}
$.fn.addTooltip = function(content, params)
{
var config = $.extend(tooltipDefaults, params);
return this.each(function() {
var element = $(this);
// If the element already has a tooltip change the content inside of it
if (element.hasTooltip()) {
$('#' + element.attr('data-tooltip-id')).html(content);
return;
}
var tooltipId = (element.is('[id]') ? element.attr('id') : generateUniqueId()) + '-tooltip';
element.attr('data-tooltip-id', tooltipId);
var tooltip = $('<div>', {
id: tooltipId,
role: 'tooltip',
class: config.class
}).html(content);
$('body').append(tooltip);
/**
* If showOn and hideOn are the same events bind a toggle
* listener else bind the individual listeners
*/
if (config.showOn === config.hideOn) {
element.on(config.showOn, function() {
if (!element.isTooltipDisplayed()) {
element.displayTooltip();
} else {
element.hideTooltip();
}
});
} else {
element.on(config.showOn, function() {
element.displayTooltip();
}).on(config.hideOn, function() {
element.hideTooltip();
});
}
// Store config for other functions use
element.data('config', config);
// Saftey check incase the element recieved focus from the code running above
element.hideTooltip();
});
}
$.fn.hasTooltip = function()
{
return $(this).is('[data-tooltip-id]');
}
$.fn.isTooltipDisplayed = function()
{
var element = $(this);
if (!element.hasTooltip()) {
return false;
}
return ($.inArray(element.attr('id'), displayedTooltips) === -1) ? false : true;
}
$.fn.removeTooltip= function()
{
return this.each(function() {
var element = $(this);
var tooltipId = element.attr('data-tooltip-id');
var config = element.data('config');
$('#' + tooltipId).remove();
if (isUniqueId(tooltpId)) {
removeUniqueId(tooltipId);
}
element.removeAttr('data-tooltip-id');
if (config.showOn === config.hideOn) {
element.off(config.showOn);
} else {
element.off(config.showOn);
element.off(config.hideOn);
}
element.removeData('config');
});
}
// Reposition tooltip on window resize
$(window).on('resize', function() {
if (displayedTooltips.length < 1) {
return;
}
for (var i = 0; i < displayedTooltips.length; i++) {
$('#' + displayedTooltips[i]).displayTooltip();
console.log(displayedTooltips);
}
});
}(jQuery));
When you do this:
element.data('config', config);
config will be prone to unwanted modification whenever we call:
var config = $.extend(tooltipDefaults, params);
An example of this: http://jsfiddle.net/j8v4s/1/
You can solve this by creating a new object that inherits from tooltipDefaults but when modified, only itself will be changed. You can make your tooltipDefaults object a constructor like so:
function TooltipDefaults() {
this.class = null;
this.showOn = 'mouseenter';
this.hideOn = 'mouseleave';
this.direction = 'top';
this.offset = 0;
}
Now we can just do this:
var config = new TooltipDefaults();
$.extend(config, params);
Example: http://jsfiddle.net/sXSFy/4/
And here's a working example of your plugin: http://jsfiddle.net/DWtL5/2/
put the declaration of config inside the each loop
return this.each(function() {
var element = $(this);
var config = $.extend(tooltipDefaults, params);
...
otherwise the config in each of the elements data is going to reference that single config object, and when you change it the changes will be seen by each of the references.
You can also use the extend method again to make a clone of the config
var defConfig = $.extend(tooltipDefaults, params);
return this.each(function() {
var element = $(this);
var config = $.extend({}, defConfig);

how to get property from other function

I want to access var w value from other function. Is it possible.
<script type="text/javascript">
var first = {
myFirst: function(){
var w= 90;
var q=12;
}}
var second= {
mySecond: function(){
first.myFirst.w
}}
</script>
In that way you cannot access w because it's defined in the local scope of myFirst. You can do something like:
var first = {
myFirst: function(){
first.myFirst.w = 90;
var q=12;
}
};
var second= {
mySecond: function(){
alert(first.myFirst.w);
}
};
first.myFirst();
second.mySecond(); //alerts 90
In that way w will be added as property of the function myFirst, if you want to add it as property of first, use:
var first = {
myFirst: function(){
this.w = 90;
var q=12;
}
};
var second= {
mySecond: function(){
alert(first.w);
}
};
first.myFirst();
second.mySecond(); //alerts 90
No, that's not possible, because the scope of w is within the function. Once the function has been called w no longer exists.
Also, technically you would need to call first.myFirst().w;
To get that to work you could do this instead:
var first = {
myFirst: function(){
var w= 90;
var q=12;
return { w : w };
}
}
first.myFirst().w // now works.

Prevent event inside object function to overwrite "this"

Game.prototype.run = function() {
window.setInterval(function() {
var thisLoop = new Date().getTime();
this.update();
this.render();
lastLoop = thisLoop;
}, 1000 / this.fps);
};
game.js:198Uncaught TypeError: Object [object DOMWindow] has no method 'update'
Why is this happening ?
"this" should relate to the Game object.
Cache the this variable, or use Function.bind:
Game.prototype.run = function() {
var _this = this;
window.setInterval(function() {
var thisLoop = new Date().getTime();
_this.update();
_this.render();
lastLoop = thisLoop;
}, 1000 / this.fps);
};
Or, using Function.bind:
Game.prototype.run = function() {
window.setInterval((function() {
...
}.bind(this), 1000 / this.fps);
};
this in a function passed to setInterval refers to the global window object, or is undefined (in strict mode).
Another method, similar to the first one. Pass this as a parameter to the function (so that no extra local variable is used):
Game.prototype.run = function() {
window.setInterval(function(_this) {
var thisLoop = new Date().getTime();
_this.update();
_this.render();
lastLoop = thisLoop;
}, 1000 / this.fps, this);
};
No, this in the scope of the function refers to the function itself. Its somewhat hard to wrap your head around scoping in JS if you're not used to it.
The easy solution is to cache the context of "this" outside the anonymous function and use that instead.
Game.prototype.run = function() {
var game = this;
window.setInterval(function() {
var thisLoop = new Date().getTime();
game.update();
game.render();
lastLoop = thisLoop;
}, 1000 / this.fps);
};
DEMO: http://jsfiddle.net/kmendes/awzMn/
In this case there's no way you can do that because the setInterval function has a different scope. This is what you can do:
Game.prototype.run = function() {
var currentGame = this;
window.setInterval(function() {
var thisLoop = new Date().getTime();
currentGame.update();
currentGame.render();
lastLoop = thisLoop;
}, 1000 / this.fps);
};
Try this :
Game.prototype.run = function() {
var intervalCallBack = function() {
var thisLoop = new Date().getTime();
this.update();
this.render();
lastLoop = thisLoop;
};
var self = this;
window.setInterval(function(){intervalCallBack.call(self);}, 1000 / this.fps);
};
Because of the fact that setInterval and setTimeout executes your callback in the global context, your this "pointer" that you used to refer to your game object is now referring to the global object (window) , which of course has no method 'update'.

Categories