jQuery .data() is being overwritten - javascript

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);

Related

Refactoring repeated code in javascript prototype constructor

I have a open function that once triggered, simply plays video in a dedicated panel.
This function can be triggered in two ways - one with a click and another one with a page load (window load) with url that contains a valid anchor tag.
They all work fine but some codes of the window load handler are repetitive and I'm not too sure how I can keep this DRY.
Please take a look and point me in some directions on how I can write this better.
I commented in open function which is for which.
$.videoWatch.prototype = {
init: function() {
this.$openLinks = this.$element.find(".open");
this.$closeLinks = this.$element.find(".close");
this.open();
this.close();
},
_getContent: function(element) {
var $parent = element.parent(),
id = element.attr('href').substring(1),
title = $parent.data('title'),
desc = $parent.data('desc');
return {
title: title,
desc: desc,
id: id
}
},
open: function() {
var self = this;
//open theatre with window load with #hash id
window.onload = function() {
var hash = location.hash;
var $a = $('a[href="' + hash + '"]'),
content = self._getContent($a),
$li = $a.parents("li"),
$theatreVideo = $(".playing"),
$theatreTitle = $(".theatre-title"),
$theatreText = $(".theatre-text");
$(".theatre").attr('id', content.id);
$theatreTitle.text(content.title);
$theatreText.text(content.desc);
if ($theatreText.text().length >= 90) {
$(".theatre-text").css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter($theatreText);
}
$a.parent().addClass("active");
$(".theatre").insertAfter($li);
$(".theatre").slideDown('fast', scrollToTheatre);
oldIndex = $li.index();
}
//open theatre with click event
self.$openLinks.on("click", function(e) {
// e.preventDefault();
if (curID == $(this).parent().attr("id")) {
$("figure").removeClass("active");
$("button.more").remove();
$(".theatre").slideUp('fast');
$('.playing').attr("src", "");
removeHash();
oldIndex = -1;
curID = "";
return false
} else {
curID = $(this).parent().attr("id");
}
var $a = $(this),
content = self._getContent($a),
$li = $a.parents("li"),
$theatreVideo = $(".playing"),
$theatreTitle = $(".theatre-title"),
$theatreText = $(".theatre-text");
$(".theatre").attr('id', content.id);
$theatreTitle.text(content.title);
$theatreText.text(content.desc);
if ($theatreText.text().length >= 90) {
$(".theatre-text").css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter($theatreText);
}
if (!($li.index() == oldIndex)) {
$("figure").removeClass("active");
$(".theatre").hide(function(){
$a.parent().addClass("active");
$(".theatre").insertAfter($li);
$(".theatre").slideDown('fast', scrollToTheatre);
oldIndex = $li.index();
});
} else {
$(".theatre").insertAfter($li);
scrollToTheatre();
$("figure").removeClass("active");
$a.parent().addClass("active");
}
});
},
...
Simplified and refactored open method:
open: function() {
var self = this;
var serviceObj = {
theatreVideo : $(".playing"),
theatre: $(".theatre"),
theatreTitle : $(".theatre-title"),
theatreText : $(".theatre-text"),
setTheatreContent: function(content){
this.theatre.attr('id', content.id);
this.theatreTitle.text(content.title);
this.theatreText.text(content.desc);
if (this.theatreText.text().length >= 90) {
this.theatreText.css({
'overflow': 'hidden',
'max-height': '90px',
});
$moreButton.insertAfter(this.theatreText);
}
},
activateTeatre: function(a, li){
a.parent().addClass("active");
this.theatre.insertAfter(li);
this.theatre.slideDown('fast', scrollToTheatre);
oldIndex = li.index();
}
};
//open theatre with window load with #hash id
window.onload = function() {
var hash = location.hash;
var $a = $('a[href="' + hash + '"]'),
content = self._getContent($a),
$li = $a.parents("li");
serviceObj.setTheatreContent(content);
serviceObj.activateTeatre($a, $li);
}
//open theatre with click event
self.$openLinks.on("click", function(e) {
// e.preventDefault();
if (curID == $(this).parent().attr("id")) {
$("figure").removeClass("active");
$("button.more").remove();
$(".theatre").slideUp('fast');
$('.playing').attr("src", "");
removeHash();
oldIndex = -1;
curID = "";
return false
} else {
curID = $(this).parent().attr("id");
}
var $a = $(this),
content = self._getContent($a),
$li = $a.parents("li");
serviceObj.setTheatreContent(content);
if (!($li.index() == oldIndex)) {
$("figure").removeClass("active");
$(".theatre").hide(function(){
serviceObj.activateTeatre($a, $li);
});
} else {
$(".theatre").insertAfter($li);
scrollToTheatre();
$("figure").removeClass("active");
$a.parent().addClass("active");
}
});
},
1st of all there are variables that don't depend on the input, you could pull them to the class (I'll show just one example, as you asked for directions):
init: function() {
this.$theatreVideo = $(".playing");
All the variables that do depend on the input, like $li could be moved to a function:
var $a = $(this),
$dependsOnA = self.dependsOnA($a);
self.actionDependsOnA($dependsOnA); // see below
function dependsOnA($a) {
return {
a: $a,
li: $a.parents("li"),
content: self._getContent($a)
}
}
Also the code that "repeats" can be moved to a function:
function actionDependsOnA($dependsOnA)
$(".theatre").attr('id', $dependsOnA.content.id);
$theatreTitle.text($dependsOnA.content.title);
$theatreText.text($dependsOnA.content.desc);
}

Solution of my Mozilla browser?

It works in chrome.But when i use with Mozilla Firefox then it shows this
" A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
Script: http://localhost/tsdev_v2/resources/js/libs/jquery-1.8.3.js:6841"
My code is...
<script>
/*$("table").stickyTableHeaders();*/
/* $(function(){
$("table").stickyTableHeaders();
});*/
$(document).ready(function() {
$("table").stickyTableHeaders({fixedOffset:40});
});
</script>
<script>
;(function ($, window, undefined) {
'use strict';
var name = 'stickyTableHeaders',
id = 0,
defaults = {
fixedOffset: 0,
leftOffset: 0,
marginTop: 0,
objDocument: document,
objHead: 'head',
objWindow: window,
scrollableArea: window
};
function Plugin (el, options) {
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
base.id = id++;
// Listen for destroyed, call teardown
base.$el.bind('destroyed',
$.proxy(base.teardown, base));
// Cache DOM refs for performance reasons
base.$clonedHeader = null;
base.$originalHeader = null;
// Keep track of state
base.isSticky = false;
base.hasBeenSticky = false;
base.leftOffset = null;
base.topOffset = null;
base.init = function () {
base.setOptions(options);
base.$el.each(function () {
var $this = $(this);
// remove padding on <table> to fix issue #7
$this.css('padding', 0);
base.$originalHeader = $('thead:first', this);
base.$clonedHeader = base.$originalHeader.clone();
$this.trigger('clonedHeader.' + name, [base.$clonedHeader]);
base.$clonedHeader.addClass('tableFloatingHeader');
base.$clonedHeader.css('display', 'none');
base.$originalHeader.addClass('tableFloatingHeaderOriginal');
base.$originalHeader.after(base.$clonedHeader);
base.$printStyle = $('<style type="text/css" media="print">' +
'.tableFloatingHeader{display:none !important;}' +
'.tableFloatingHeaderOriginal{position:static !important;}' +
'</style>');
base.$head.append(base.$printStyle);
});
base.updateWidth();
base.toggleHeaders();
base.bind();
};
base.destroy = function (){
base.$el.unbind('destroyed', base.teardown);
base.teardown();
};
base.teardown = function(){
if (base.isSticky) {
base.$originalHeader.css('position', 'static');
}
$.removeData(base.el, 'plugin_' + name);
base.unbind();
base.$clonedHeader.remove();
base.$originalHeader.removeClass('tableFloatingHeaderOriginal');
base.$originalHeader.css('visibility', 'visible');
base.$printStyle.remove();
base.el = null;
base.$el = null;
};
base.bind = function(){
base.$scrollableArea.on('scroll.' + name, base.toggleHeaders);
if (!base.isWindowScrolling) {
base.$window.on('scroll.' + name + base.id, base.setPositionValues);
base.$window.on('resize.' + name + base.id, base.toggleHeaders);
}
base.$scrollableArea.on('resize.' + name, base.toggleHeaders);
base.$scrollableArea.on('resize.' + name, base.updateWidth);
};
base.unbind = function(){
// unbind window events by specifying handle so we don't remove too much
base.$scrollableArea.off('.' + name, base.toggleHeaders);
if (!base.isWindowScrolling) {
base.$window.off('.' + name + base.id, base.setPositionValues);
base.$window.off('.' + name + base.id, base.toggleHeaders);
}
base.$scrollableArea.off('.' + name, base.updateWidth);
};
base.toggleHeaders = function () {
if (base.$el) {
base.$el.each(function () {
var $this = $(this),
newLeft,
newTopOffset = base.isWindowScrolling ? (
isNaN(base.options.fixedOffset) ?
base.options.fixedOffset.outerHeight() :
base.options.fixedOffset
) :
base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0),
offset = $this.offset(),
scrollTop = base.$scrollableArea.scrollTop() + newTopOffset,
scrollLeft = base.$scrollableArea.scrollLeft(),
scrolledPastTop = base.isWindowScrolling ?
scrollTop > offset.top :
newTopOffset > offset.top,
notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) <
(offset.top + $this.height() - base.$clonedHeader.height() - (base.isWindowScrolling ? 0 : newTopOffset));
if (scrolledPastTop && notScrolledPastBottom) {
newLeft = offset.left - scrollLeft + base.options.leftOffset;
base.$originalHeader.css({
'position': 'fixed',
'margin-top': base.options.marginTop,
'left': newLeft,
'z-index': 3 // #18: opacity bug
});
base.leftOffset = newLeft;
base.topOffset = newTopOffset;
base.$clonedHeader.css('display', '');
if (!base.isSticky) {
base.isSticky = true;
// make sure the width is correct: the user might have resized the browser while in static mode
base.updateWidth();
$this.trigger('enabledStickiness.' + name);
}
base.setPositionValues();
} else if (base.isSticky) {
base.$originalHeader.css('position', 'static');
base.$clonedHeader.css('display', 'none');
base.isSticky = false;
base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader));
$this.trigger('disabledStickiness.' + name);
}
});
}
};
base.setPositionValues = function () {
var winScrollTop = base.$window.scrollTop(),
winScrollLeft = base.$window.scrollLeft();
if (!base.isSticky ||
winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() ||
winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) {
return;
}
base.$originalHeader.css({
'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop),
'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft)
});
};
base.updateWidth = function () {
if (!base.isSticky) {
return;
}
// Copy cell widths from clone
if (!base.$originalHeaderCells) {
base.$originalHeaderCells = $('th,td', base.$originalHeader);
}
if (!base.$clonedHeaderCells) {
base.$clonedHeaderCells = $('th,td', base.$clonedHeader);
}
var cellWidths = base.getWidth(base.$clonedHeaderCells);
base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells);
// Copy row width from whole table
base.$originalHeader.css('width', base.$clonedHeader.width());
};
base.getWidth = function ($clonedHeaders) {
var widths = [];
$clonedHeaders.each(function (index) {
var width, $this = $(this);
if ($this.css('box-sizing') === 'border-box') {
var boundingClientRect = $this[0].getBoundingClientRect();
if(boundingClientRect.width) {
width = boundingClientRect.width; // #39: border-box bug
} else {
width = boundingClientRect.right - boundingClientRect.left; // ie8 bug: getBoundingClientRect() does not have a width property
}
} else {
var $origTh = $('th', base.$originalHeader);
if ($origTh.css('border-collapse') === 'collapse') {
if (window.getComputedStyle) {
width = parseFloat(window.getComputedStyle(this, null).width);
} else {
// ie8 only
var leftPadding = parseFloat($this.css('padding-left'));
var rightPadding = parseFloat($this.css('padding-right'));
// Needs more investigation - this is assuming constant border around this cell and it's neighbours.
var border = parseFloat($this.css('border-width'));
width = $this.outerWidth() - leftPadding - rightPadding - border;
}
} else {
width = $this.width();
}
}
widths[index] = width;
});
return widths;
};
base.setWidth = function (widths, $clonedHeaders, $origHeaders) {
$clonedHeaders.each(function (index) {
var width = widths[index];
$origHeaders.eq(index).css({
'min-width': width,
'max-width': width
});
});
};
base.resetWidth = function ($clonedHeaders, $origHeaders) {
$clonedHeaders.each(function (index) {
var $this = $(this);
$origHeaders.eq(index).css({
'min-width': $this.css('min-width'),
'max-width': $this.css('max-width')
});
});
};
base.setOptions = function (options) {
base.options = $.extend({}, defaults, options);
base.$window = $(base.options.objWindow);
base.$head = $(base.options.objHead);
base.$document = $(base.options.objDocument);
base.$scrollableArea = $(base.options.scrollableArea);
base.isWindowScrolling = base.$scrollableArea[0] === base.$window[0];
};
base.updateOptions = function (options) {
base.setOptions(options);
// scrollableArea might have changed
base.unbind();
base.bind();
base.updateWidth();
base.toggleHeaders();
};
// Run initializer
base.init();
}
// A plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[name] = function ( options ) {
return this.each(function () {
var instance = $.data(this, 'plugin_' + name);
if (instance) {
if (typeof options === 'string') {
instance[options].apply(instance);
} else {
instance.updateOptions(options);
}
} else if(options !== 'destroy') {
$.data(this, 'plugin_' + name, new Plugin( this, options ));
}
});
};
})(jQuery, window);
</script>
In my jquery-1.8.3.js:6841
if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
width = style.width;
minWidth = style.minWidth;
maxWidth = style.maxWidth;
style.minWidth = style.maxWidth = style.width = ret;
ret = computed.width;
style.width = width;
style.minWidth = minWidth;
style.maxWidth = maxWidth;
}
How to recover this ? Please help.

jQuery Appear Function

I'm trying to use the jQuery appear plugin. I'm having trouble making it work. I tried to attach it to the (window).scroll event but it makes the page slow. If I don't use the scroll, it only fires once. I need it to work again whenever the element becomes visible. Can you give me some tips on how to make it work.
Here's my code:
jQuery('.home-section-1').appear(function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
As ɴ-ᴀ-ᴛ-ʜ said in his comment, you need to be using .on to listen for the appear event.
jQuery('.home-section-1').on('appear', function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
Here's a code snippit showing it working, you'll notice that your method (Method 1) doesn't fire, while the method above (Method 2) does:
/*
* jQuery appear plugin
*
* Copyright (c) 2012 Andrey Sidorov
* licensed under MIT license.
*
* https://github.com/morr/jquery.appear/
*
* Version: 0.3.4
*/
(function($) {
var selectors = [];
var check_binded = false;
var check_lock = false;
var defaults = {
interval: 250,
force_process: false
}
var $window = $(window);
var $prior_appeared;
function process() {
check_lock = false;
for (var index = 0, selectorsLength = selectors.length; index < selectorsLength; index++) {
var $appeared = $(selectors[index]).filter(function() {
return $(this).is(':appeared');
});
$appeared.trigger('appear', [$appeared]);
if ($prior_appeared) {
var $disappeared = $prior_appeared.not($appeared);
$disappeared.trigger('disappear', [$disappeared]);
}
$prior_appeared = $appeared;
}
}
// "appeared" custom filter
$.expr[':']['appeared'] = function(element) {
var $element = $(element);
if (!$element.is(':visible')) {
return false;
}
var window_left = $window.scrollLeft();
var window_top = $window.scrollTop();
var offset = $element.offset();
var left = offset.left;
var top = offset.top;
if (top + $element.height() >= window_top &&
top - ($element.data('appear-top-offset') || 0) <= window_top + $window.height() &&
left + $element.width() >= window_left &&
left - ($element.data('appear-left-offset') || 0) <= window_left + $window.width()) {
return true;
} else {
return false;
}
}
$.fn.extend({
// watching for element's appearance in browser viewport
appear: function(options) {
var opts = $.extend({}, defaults, options || {});
var selector = this.selector || this;
if (!check_binded) {
var on_check = function() {
if (check_lock) {
return;
}
check_lock = true;
setTimeout(process, opts.interval);
};
$(window).scroll(on_check).resize(on_check);
check_binded = true;
}
if (opts.force_process) {
setTimeout(process, opts.interval);
}
selectors.push(selector);
return $(selector);
}
});
$.extend({
// force elements's appearance check
force_appear: function() {
if (check_binded) {
process();
return true;
};
return false;
}
});
})(jQuery);
// Your method
jQuery('.home-section-1').appear(function(){
alert('Method 1');
});
// Using .on
jQuery('.home-section-1').on('appear', function(){
alert('Method 2');
});
.home-section-1 {
margin-top: 2000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="home-section-1">Hello World</div>

Js position button in bottom right corner instead of centered

I'm trying to position this button for my quickview in the bottom right corner of the image instead of centered, but I can't figure out how to do it. The code is using top and left offsets to position the button in the center. How can I position it in the bottom right corner instead? To see an example of the positioning I'm hoping to achieve, go to: http://www.shrimptoncouture.com/collections/all-clothing.
Here's what I'm working with. Any help would be greatly appreciated:
Cmsmart.noConflict();
Cmsmart(function($) {
var producturl;
function geturlrewrite(){
var mypath = arguments[0];
var patt = /\/[^\/]{0,}$/ig;
if(mypath){
if(mypath[mypath.length-1]=="/"){
mypath = mypath.substring(0,mypath.length-1);
return (mypath.match(patt)+"/");
}
return mypath.match(patt);
}
return '';
}
function urltrim(){
return arguments[0].replace(/^\s+|\s+$/g,"");
}
function installquickview(){
if (typeof CMSMART == 'undefined') return;
var argInput = arguments[0];
var productlistBlocks = $(argInput.productlistClassArr);
var datasaved = [];
var mypath = 'cmsmquickview/index/index';
if(CMSMART.QuickView.BASE_URL.indexOf('index.php') == -1){
mypath = 'cmsmquickview/index/index';
}else{
mypath = 'cmsmquickview/index/index';
}
var baseUrl = CMSMART.QuickView.BASE_URL + mypath;
var _quickviewbutton = '<a id="cmsmart_quickview_button" href="#">' + CMSMART.QuickView.BOTTON_LABEL + '</a>';
var _quickform = '<div id="csmm_quickform">' +
'<div id = "quickviewshow" ></div>' +
'</div>';
$(document.body).append(_quickform);
$(document.body).append(_quickviewbutton);
var quickviewButton = $('#cmsmart_quickview_button');
//alert(encodeURIComponent(CMSMART.QuickView.BASE_URL + 'ab=3dfd&ddfdfd=234'));
$.each(productlistBlocks, function(i, vl){
var productlist = $(vl);
$.each(productlist, function(index, value) {
var reloadurl = '';
var aClass = argInput.aClass[i]?argInput.aClass[i]:argInput.aClass[0];
producturl = $(aClass, value);
if(producturl.attr('href')){
var producturlpath = producturl.attr('href').replace(CMSMART.QuickView.BASE_URL,"");
//var producturlpath = geturlrewrite(producturl.attr('href'))[0];
//producturlpath[0] == "\/" ? producturlpath = producturlpath.substring(1,producturlpath.length) : producturlpath;
//producturlpath = urltrim(producturlpath);
reloadurl += baseUrl+ ("/path/"+producturlpath).replace(/\/\//g,"/");
//alert(reloadurl);
var imgClass = argInput.imgClass[i]?argInput.imgClass[i]:argInput.imgClass[0];
$(this).bind('mouseover', function() {
//var o = $(this).offset();
//var o = $(this);
var o = $(imgClass+':eq(0)', this);
$('#cmsmart_quickview_button').attr('href',reloadurl).show()
.css({
'top': o.offset().top+(o.height() - quickviewButton.height())/2+'px',
'left': o.offset().left+(o.width() - quickviewButton.outerWidth())/2+'px',
'visibility': 'visible'
});
});
$(value).bind('mouseout', function() {
$('#cmsmart_quickview_button').hide();
});
}
});
});
if(CMSMART.QuickView.CENTER)
{
$("#quickviewshow").css('margin', ($(window).height() / 2 - $("#quickviewshow").height() / 2) + "px auto auto auto");
}
$('#cmsmart_quickview_button')
.bind('mouseover', function() {
$(this).show();
})
.bind('click', function() {
idbyurl = ($(this).attr('href')).replace(/\W/g,"");
showqv();
$("#quickviewshow").html('<a id="cmsmart_quickview_button_close" title="Close Quick View"> </a><a class="quickviewloading"><a>');
$("#cmsmart_quickview_button_close").on( "click", function() {
closeqv();
$("div.zoomContainer").remove();
});
$(this).hide();
if(datasaved[idbyurl]){
$("#quickviewshow").html('<a id="cmsmart_quickview_button_close" title="Close Quick View"> </a>');
$("#cmsmart_quickview_button_close").on( "click", function() {
closeqv();
});
$("#quickviewshow").append(datasaved[idbyurl]);
showqv();
relimg();
return false;
}
else{
$.ajax({
url: $(this).attr('href'),
cache: false
}).done(function( html ) {
$("#quickviewshow").html('<a id="cmsmart_quickview_button_close" title="Close Quick View"> </a>');
$("#cmsmart_quickview_button_close").on( "click", function() {
closeqv();
$("div.zoomContainer").remove();
});
$("#quickviewshow").append(html);
showqv();
datasaved[idbyurl] = html;
relimg();
});
}
return false;
});
$('#csmm_quickform').click(function(e) {
if($(e.target).is('#quickviewshow, #quickviewshow *')) return;
$('#csmm_quickform').hide();
$("div.zoomContainer").remove();
});
}
$( document ).ready(function() {
installquickview(CMSMART.QuickView.BUTTON_CONFIG);
});
$(window).resize(function() {
$("#quickviewshow").css('margin', ($(window).height() / 2 - $("#quickviewshow").height() / 2) + "px auto auto auto");
});
function relimg(){
maxh = $('div.product-quickview').outerHeight() - 45;
if($('div.qvtabhead')) maxh = maxh - $('div.qvtabhead').outerHeight();
if($('div.qvformaddtocart')) maxh = maxh - $('div.qvformaddtocart').outerHeight();
if($('div.tabquickshow')) $('div.tabquickshow').css('max-height', maxh + "px");
//submitbqv();
$('#showlargeimg').elevateZoom({ zoomWindowWidth:300, zoomWindowHeight:300, borderSize: 2, zoomWindowOffetx:15, cursor:'move' });
$('#mycarousel').jcarousel({
scroll: 4
});
$("li img.p_image_hover").click(
function () {
smallImage = $(this).attr('src');
largeImage = $(this).attr('data-zoom-image');
$('img#showlargeimg').attr('src', smallImage);
var ez = $('#showlargeimg').data('elevateZoom');
ez.swaptheimage(smallImage, largeImage);
}
);
$('a.tabquickviewcontrol').click(
function(){
$('a.tabquickviewcontrol').removeClass("highlight");
$(this).addClass("highlight");
var divsl = $(this).attr('href');
$('.tabquickshow').css('display', 'none');
$(divsl).css('display', 'block');
return false;
}
)
}
function showqv(){ $("#csmm_quickform").css("display", "block"); }
function closeqv(){ $("#csmm_quickform").css("display", "none"); }
function btcloseqv(){
$("#cmsmart_quickview_button_close").on( "click", function() {
$("#csmm_quickform").css("display", "none");
});
}
function submitbqv(){
var fr = $('#product_addtocart_form');
var btc = $('.btn-cart', fr);
btc.attr('onclick', '');
btc.click(function(){
var cansubmit = true;
$('select.required-entry', fr).each(function(){
if($(this).val() == ''){
$(this).addClass('validation-failed');
$(this).focus();
cansubmit = false;
return false;
}else { $(this).removeClass('validation-failed'); }
});
if(cansubmit) fr.submit();
});
}
btcloseqv();
});
It might be something like:
$('#cmsmart_quickview_button').attr('href',reloadurl).show()
.css({
'top' : o.offset().top + o.height() - quickviewButton.height()+'px',
'left': o.offset().left+ o.width() - quickviewButton.outerWidth()+'px',
'visibility': 'visible'
});
If that isn't it, keep playing with that code until you get what you want. You might try o.offset().bottom and o.offset().right but I don't know if they are defined.

uncaught TypeError: Cannot read property "top" of null

I got a pretty annoying javascript error. The world famous:
uncaught TypeError: Cannot read property "top" of null
Here is the code:
$(function() {
var setTitle = function(title, href) {
title = 'Derp: ' + title;
href = href || '';
history.pushState({id: href}, title, href.replace('#', '/'));
document.title = title;
},
scroll = function(url, speed) {
var href = typeof url == 'string' ? url : $(this).attr('href'),
target = $(href),
offset = target.offset(),
title = target.find('h1').text();
if(typeof url == 'number') {
target = [{id:''}];
offset = {top: url};
}
// And move the element
if(offset.top) {
// Set the new URL and title
setTitle(title, href);
// Make sure we're not moving the contact panel
if(target[0].id != 'contact') {
$('html, body').animate({scrollTop: offset.top}, speed);
}
}
return false;
};
// Handle existing URL fragments on load
if(location.pathname.length > 1) {
scroll(location.pathname.replace('/', '#'), 0);
}
$('a#logo').click(function() {
$('html,body').animate({scrollTop: 0});
return false;
});
// Handle internal link clicks
$('a[href^=#]:not(#logo)').click(scroll);
// Close the "Get In Touch" box
var box = $('#contact'),
moveBox = function() {
var closing = $(this).attr('class') == 'close',
amount = closing ? -(box.height() + 20) : 0,
cb = closing ? '' : function() { box.animate({marginTop: -10}, 150); };
box.animate({marginTop: amount}, cb);
};
box.css('margin-top', -(box.height() + 20));
$('#contact a.close, #get-in-touch').click(moveBox);
// Nasty little fix for vertical centering
$('.vertical').each(function() {
$(this).css('margin-top', -($(this).height() / 2));
});
// Work panels
var parent = $('#work'),
panels = parent.children('div');
panels.each(function() {
$(this).css('width', 100 / panels.length + '%');
})
parent.css('width', (panels.length * 100) + '%');
// Bind the keyboards
$(document).keyup(function(e) {
var actions = {
// Left
37: function() {
var prev = panels.filter('.active').prev().not('small');
if(prev.length > 0) {
prev.siblings().removeClass('active');
setTitle(prev.find('h1').text(), prev[0].id);
setTimeout(function() {
prev.addClass('active');
}, 250);
parent.animate({left: '+=100%'}).css('background-color', '#' + prev.attr('data-background'));
}
},
// Right
39: function() {
var next = panels.filter('.active').next();
if(next.length > 0) {
next.siblings().removeClass('active');
setTitle(next.find('h1').text(), next[0].id);
setTimeout(function() {
next.addClass('active');
}, 250);
parent.animate({left: '-=100%'}).css('background-color', '#' + next.attr('data-background'));
}
},
// Down
40: function() {
var w = $(window),
height = w.height() * panels.children('div').length,
h = w.height() + w.scrollTop();
if(h < height) {
scroll(h);
}
},
// Up
38: function() {
var w = $(window);
$('html,body').animate({scrollTop: w.scrollTop() - w.height()});
}
};
// Call a function based on keycode
if(actions[e.which]) {
actions[e.which]();
}
e.preventDefault();
return false;
});
// Fix crazy resize bugs
$(window).resize(function() {
var m = $(this),
h = m.height(),
s = m.scrollTop();
if((h - s) < (h / 2)) {
m.scrollTop(h);
}
//$('html,body').animate({scrollTop: s});
});
// slideshow
var woof = function() {
var slides = $('#molly li'),
active = slides.filter('.active');
if(!active.length) {
active = slides.last();
}
active.addClass('active');
var next = active.next().length ? active.next() : slides.first();
next.css('opacity', 0).addClass('active').animate({opacity: 1}, function() {
active.removeClass('active last-active');
});
};
setInterval(woof, 3000);
// easing
$.easing.swing = function(v,i,s,u,a,l) {
if((i /= a / 2) < 1) {
return u / 2 * (Math.pow(i, 3)) + s;
}
return u / 2 * ((i -= 2) * i * i + 2) + s;
};
// Change the default .animate() time: http://forr.st/~PG0
$.fx.speeds._default = 600;
});
try{Typekit.load()}catch(e){}
Sorry for this long monster but I thought it could be useful for you to see the whole thing. The Error warning shows up in this part:
// And move the element
if(offset.top) {
Uncaught TypeError: Cannot read property 'top' of null
It's line 23 in the code.
That's it. Could you give me a hint on how to solve this problem?
Thank you!
var href = typeof url == 'string' ? url : $(this).attr('href'),
target = $(href), //line 2
offset = target.offset(), //line 3
I believe this must have something to do with line 2, target should be null when error occurs
According to jQuery source, jQuery.fn.offset only returns null if:
the first element in the set doesn't exist (empty set) or
its ownerDocument is falsy (I don't know when that would happen, sorry).
The first option seems more likely, so you should check if target.length > 0 before calling target.offset() and handle the alternative.

Categories