Get target of .each function within a $.proxy function - javascript

I'm using RequireJS to structure my JS which is why I'm using a $.proxy function. What I'm trying to do within the .each function is give each carousel on the page a unique data attribute. In order to do this I have to be able to target each carousel, but $(this) in this context doesn't target each carousel. I've tried using currentTarget but I think this only works on click events?
Below is the specific snippet which needs fixing.
getCarousels: function() {
$carousel.each($.proxy(function(index) {
$owl = (index.currentTarget);
$owl.attr('data-number', index);
this.initCarousel();
},this));
},
And here is the full file.
define(['owlcarousel'], function() {
var $ = jQuery,
$carousel = $('.carousel'),
$data = $('.carousel-data');
function updateResult(pos, value, carouselNumber) {
$(carouselNumber).find(pos).text(value);
}
function afterAction(el) {
var owl = el.data('owlCarousel'),
actualValue = this.owl.currentItem + 1,
carouselNumber = el.attr('data-number');
console.log(carouselNumber);
updateResult('.carousel-current', actualValue, carouselNumber);
updateResult('.carousel-items', this.owl.owlItems.length, carouselNumber);
}
return {
init: function() {
if($carousel.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.getCarousels();
},
getCarousels: function() {
$carousel.each($.proxy(function(index) {
$owl = (index.currentTarget);
$owl.attr('data-number', index);
this.initCarousel();
},this));
},
initCarousel: function() {
$carousel.owlCarousel({
singleItem: true,
autoPlay: true,
slideSpeed: 400,
paginationSpeed: 800,
lazyLoad: true,
afterAction: afterAction
});
}
};
});

.each receive 2 parameters which is the index and the value. Usualy the value is the same as this, but well, you overrided it. Get that second parameter! :
$carousel.each($.proxy(function(index, el) {
$owl = $(el);
$owl.attr('data-number', index);
this.initCarousel();
},this));
But why not simply not use proxy?
var that = this
$carousel.each(function(index) {
$owl = $(this);
$owl.attr('data-number', index);
that.initCarousel();
});

Related

Error: cannot call methods on tabs prior to initialization; attempted to call method 'destroy'

I'm working on updating an older site to the newer version of jQuery and applying bootstrap.
We are using
backbone.js
jQueryui and
bootstrap.js.
I am getting the following error: Error: cannot call methods on tabs prior to initialization; attempted to call method 'destroy'
The lines of code it is related to is:
Thanks
define([
'jquery',
'jqueryui',
'underscore',
'backbone',
'vm',
'events',
'models/product',
'text!templates/editor/page.html'
], function ($, jui, _, Backbone, Vm, Events, Product, PageTemplate) {
var EditorPage = Backbone.View.extend({
model: Product,
el: '#editor',
_$editorErrorContainer: undefined,
events: {
"click #toolbar": "onToolbarClicked"
},
initialize: function () {
this.bindSaveEvents();
Events.unbind("addText");//HACK: Prevents zombie listeners for this specific situation
Events.bind("addText", this.onAddText, this);
},
onAddText: function (e) {
var layers = this.model.get('layers');
var textCollections = [];
//Search for valid text collections in our layer and push into our array
layers.models.forEach(function (layer) {
var tc = layer.get("textCollection");
if (typeof tc !== "undefined") {
if (layer.get("allowText")) {
var printColor = layer.get("printColor");
var src;
if(printColor) { src = printColor.get("src"); }
var _tc = { 'textCollection': tc, 'src': src, 'name':layer.get('name'), 'cid': layer.cid };
textCollections.push(_tc);
}
}
});
//if there's no choice to make, just add the text element.
if (textCollections.length == 1) {
var textCollection = textCollections[0].textCollection;
AddTextToCollection(textCollection);
return;
}
//Otherwise create dialog for choosing in which text collection to put a new text object
var dialogHTML = "<div> <p>What color would you like this text?</p>";
//Generate selections, use the src attribute of the print color if it uses printcolor
//TODO use other attributes of a textCollection such as rgb color if it uses it etc
textCollections.forEach(function (textCollection, i) {
var imgurl = "";
if(textCollection.src) {
imgurl = "/productEditor/assets/printcolors/icons/" + textCollection.src;
}
//Customers wont like zero indexed option names
var id = i + 1;
dialogHTML += "<p data-id= " + i + " class='selection'> Group " + id + ": " + textCollection.name + "<img src='" + imgurl + "'/> </p>";
});
dialogHTML += "</div>"
$(dialogHTML).dialog({
modal: true,
width: "25%",
height: "auto",
dialogClass: "textAddDialog",
resizable: false,
position: {
my: 'left top',
at: 'left top',
of: $("#editor"),
collision: 'flip'
},
show: 'fade',
create: function() {
var that = this;
//bind dialog events
$(this).children(".selection").click(function(){
$(that).children('.selected').removeClass("selected");
$(this).addClass("selected");
});
},
open: function (event, ui) {
$('.ui-dialog').css('z-index',2003);
$('.ui-widget-overlay').css({
'z-index': 2002,
'opacity': 0.5
});
},
close: function() {
$(this).children(".selection").unbind();
$(this).dialog('destroy').remove();
},
buttons: [{
text: "Accept",
"class": "acceptButton",
click: function() {
var index = $(this).children('.selected').data('id');
if(index >= 0) {
var textCollection = textCollections[index].textCollection;
AddTextToCollection(textCollection);
}
$(this).dialog("close");
}
},
{
text: "Cancel",
click: function() {
$(this).dialog("close");
}
}
]
});
function AddTextToCollection (textCollection) {
//Create text element in chosen group
var entry = textCollection.createEntry();
//Show user that this is a fresh text element that needs changing
entry.set("text", "Edit Me!");
entry.set("freshElement", true);//Fresh element denotes a completely new element, used to delete this element if user adds an element -> cancel button
//Trigger mouseup on element so our text editor dialog will pop for this element
var elem = $('.text_modifier[data-cid="' + entry.cid + '"]');
elem.trigger("click");
}
},
bindSaveEvents: function(){
Events.on('saveError', this.onSaveError.bind(this));
Events.on('saveSuccess', this.onSaveSuccess.bind(this));
},
unbindSaveEvents: function(){
Events.off('saveError', this.onSaveError.bind(this));
Events.off('saveSuccess', this.onSaveSuccess.bind(this));
},
onToolbarClicked: function (e) {
//console.log("toolbar clicked");
//this._productView.clearSelectedChildren();
},
onSaveError: function(errorMessage){
// if(this._$editorErrorContainer === undefined){
// this._$editorErrorContainer = $("<div/>").addClass("error");
// $(this.el).before(this._$editorErrorContainer);
// }
// this._$editorErrorContainer.text(errorMessage).show();
$('#appError').text(errorMessage).show();
},
onSaveSuccess: function(){
// if(this._$editorErrorContainer != null){
// this._$editorErrorContainer.hide();
// }
$('#appError').hide();
},
onRenderComplete: function (e) {
// ADD THE RETURN ELEMENT TO THE LIST OF
this._loadList = _.without(this._loadList, e);
if (this._loadList.length == 0) {
$(this.el).removeClass('loading');
}
},
remove: function(){
this.unbindSaveEvents();
},
renderApp: function () {
var that = this;
// PUT LIST OF ITEMS THAT RENDER INTO ARRAY
// renderComplete WILL REMOVE ITEMS, AND CHANGE STATE TO VISIBLE WHEN ALL ARE LOADED
this._loadList = ['layer', 'product', 'modifiers', 'tabs']
// CREATE PREVIEW MODIFIERS
require(['views/modifiers/product'], function (PreviewModifierView) {
that._modifierView = new PreviewModifierView({ model: that.model });
that._modifierView.on("renderComplete", that.onRenderComplete, that);
that._modifierView.render();
});
// CREATE PREVIEW
require(['views/preview/product'], function (PreviewProductView) {
that._productView = new PreviewProductView({ model: that.model });
that._productView.on("renderComplete", that.onRenderComplete, that);
that._productView.render();
});
// CREATE PRODUCT NAME DISPLAY
require(['views/toolbar/productname'], function (ProductNameView) {
that._productNameView = new ProductNameView({ model: that.model });
that._productNameView.render();
});
// CREATE PRODUCT DESCRIPTION DISPLAY
require(['views/toolbar/productdescription'], function (ProductDescView) {
that._productDescriptionView = new ProductDescView({ model: that.model });
that._productDescriptionView.render();
});
// CREATE TOOLBAR
require(['views/toolbar/layer'], function (ToolbarLayerView) {
that._toolbarView = new ToolbarLayerView({ model: that.model });
that._toolbarView.on("renderComplete", that.onRenderComplete, that);
that._toolbarView.render();
});
// CREATE FINISHED BUTTON
require(['views/toolbar/finished'], function (FinishedView) {
that._finishedView = new FinishedView({ model: that.model });
that._finishedView.render();
});
// CREATE TABS
require(['views/tabs/product'], function (TabsView) {
that._tabsView = new TabsView({ model: that.model });
that._tabsView.on("renderComplete", that.onRenderComplete, that);
that._tabsView.render();
});
},
render: function () {
//console.log("editor render!");
//console.log(this.model);
//$('#productName').html(this.model.get);
var pageTemplate = _.template(PageTemplate, this);
$(this.el).html(pageTemplate);
$('#button-editor-help').show(); // GET'S HIDDEN ON THE APPROVAL PAGE
this.renderApp();
}
});
return EditorPage;
});
In the tabs view, hook up a debugger where you call the destroy and create
Replicate the bug and check if you are calling destroy before initialization
May be you are calling close before the dialog is initialised.

Need to Resolve: Uncaught TypeError: Cannot call method 'each' of undefined

I purchased a template from themeforest and am having a "Uncaught TypeError: Cannot call method 'each' of undefined" after I removed two of the three opening sliders. The error slows the page load time down to a crawl however the entire site is functional. Does 'each' reference the 3 original sliders and if so can I edit it to reference the 1 lone slider?
function init_main_slider(target) {
set_height();
jQuery(target).flexslider({
animation : 'fade',
controlNav : true,
directionNav : true,
animationLoop : true,
slideshow : false,
animationSpeed : 500,
useCSS : true,
start : function(slider) {
if(!isMobile) {
slider.slides.each(function(s) {
jQuery(this).find('.animated_item').each(function(n) {
jQuery(this).addClass('animate_item' + n);
});
});
slider.slides.eq(slider.currentSlide).find('.animated_item').each(function(n) {
var show_animation = jQuery(this).attr('data-animation');
jQuery(this).addClass(show_animation);
});
}
else {
slider.find('.counter').find('.num').each(function() {
var container = jQuery(this);
var num = container.attr('data-num');
var content = container.attr('data-content');
count_num(num, content, container, false);
});
}
},
before : function(slider) {
if(!isMobile) {
slider.slides.eq(slider.currentSlide).find('.animated_item').each(function(n) {
var show_animation = jQuery(this).attr('data-animation');
jQuery(this).removeClass(show_animation);
});
slider.slides.find('.animated_item').hide();
var counter_block = slider.slides.eq(slider.currentSlide).find('.counter');
if(counter_block.length > 0) {
setTimeout(function() {
counter_block.find('.num').each(function() {
jQuery(this).html('0');
});
}, 300);
}
}
},
after : function(slider) {
if(!isMobile) {
slider.slides.find('.animated_item').show();
slider.slides.eq(slider.currentSlide).find('.animated_item').each(function(n) {
var show_animation = jQuery(this).attr('data-animation');
jQuery(this).addClass(show_animation);
});
var counter_block = slider.slides.eq(slider.currentSlide).find('.counter');
if(counter_block.length > 0) {
counter_block.find('.num').each(function() {
var container = jQuery(this);
var num = container.attr('data-num');
var content = container.attr('data-content');
count_num(num, content, container, 1500);
});
}
}
}
});
function set_height() {
var w_height = jQuery(window).height();
jQuery(target).height(w_height).find('.slides > li').height(w_height);
}
jQuery(window).resize(function() {
set_height();
});
}
How did you "remove the sliders"? I noticed in the html that the slide still has a class of <li class="slide_3"> which might be causing issues for the plugin. Does it make a difference to remove that or change to just slide or slide_1? If you cant get it to cooperate one idea is that there really is no need for this slider when only have one image so you could count the number of <li> and only use that function when there is more than one image, otherwise insert the single image... something like.
jQuery(function() {
// count the list
var listSize = $(".slides li").size();
if (listSize < 2) {
$('#main_slider').prepend('<img src="images/pic_slider_1_3.png" alt="">')
} else {
init_main_slider('#main_slider');
}
});

Backbone events not firing after on demand loading element

I'm using backbone and lazy loading views in a single page application as I need them. However, it appears doing this seems to be confusing the way backbone knows what my 'el' is when setting up events. Using the view definition below, I'm trying to get the code that fires on the submit button click or the input fields changing but right now, neither appear to work.
$(document).ready(function () {
editaddressView = Backbone.View.extend({
elementReady: false,
initialize: function () {
this.model = window.AccountData;
this.listenTo(this.model, "change", this.render);
if ($('#section-editaddress').length == 0) {
// Load UI
$('#ajax-sections').prepend('<div class="section" id="section-editaddress" style="display: none;"></div>');
}
this.el = $('#section-editaddress');
},
events: {
"click #edit-address-submit": "beginSaving",
"change input": "updateModel",
"change select": "updateModel"
},
render: function () {
$(this.el).find("[name=address]").val(this.model.get('owner_address1'));
// ...
return this;
},
switchTo: function () {
// Set menu state
$('.js-NavItem').removeClass('active');
$('#sN-li-personal').addClass('active');
if (this.options.isPreLoaded)
this.elementReady = true;
if (this.elementReady) {
this.renderSwitch();
}
else {
var model = this;
$('#section-editaddress').load('/ajax/ui/editaddress', function (response, status, xhr) {
if (status == "error") {
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeIn('fast');
});
} else {
$('#section-editaddress').find('.routedLink').click(function (e) {
window.Router.navigate($(this).attr('href'), true);
return false;
});
model.delegateEvents();
model.elementReady = true;
model.render(); // First render
model.renderSwitch();
}
});
}
},
renderSwitch: function () {
// Abort showing loading progress if possible
if (window.firstRunComplete) {
clearTimeout(window.pageHide);
// Change screen - Fade progress if needed
$('#page-progress-container').fadeOut('fast', function () {
$('#page-load-error').fadeOut('fast');
var sections = $(".section");
var numSections = sections.length;
var i = 0;
sections.hide('drop', { easing: 'easeInCubic', direction: 'left' }, 350, function () {
i++;
if (i == numSections) {
$('#section-editaddress').show('drop', { easing: 'easeInExpo', direction: 'right' }, 350).removeClass('hidden');
$.scrollTo($('#contentRegion'), 250, { margin: true });
}
});
});
}
// Switch complete
window.changingPage = false;
},
updateModel: function () {
var changedItems = {};
if (this.model.get('csrf') != $(this.el).find("[name=csrf]").val())
changedItems.csrf = $(this.el).find("[name=csrf]").val();
// ...
},
beginSaving: function () {
alert('test');
}
});
});
Can anyone see what I've missed?
Whenever you need to change or modify the DOM element of a BackboneJS view manually, you should use setElement rather than setting the property directly. It moves all of the event handlers to the newly attached DOM element and also sets the $el property. In addition, the function also detaches any existing event handlers.
So, in the code you pasted, you'd just change it to:
this.setElement($('#section-editaddress'));

Click Function on jQuery plugin only allows for single click

I've created this simple plugin to add multiple animations on click but the problem is that once the object is clicked it can not repeat the animation by clicking again, i can't figure out why the added class is not removing itself after the click function is complete to allow it to be clicked again and repeat.. any suggestions?
(function($) {
$.fn.vivify = function(options) {
var defaults = {
animation: 'bounce',
};
var options = $.extend(defaults, options);
return this.each(function() {
var o = options;
var obj = $(this);
var animation = o.animation;
obj.bind("click", function() {
obj.addClass(o.animation);
obj.addClass('vivify');
}, function() {
obj.removeClass(o.animation);
});
})
}
})(jQuery);​
Here's a working example (I guess, because I don't know exactly what's the intended effect of your plugin):
http://jsfiddle.net/gabrieleromanato/Fmr9g/
obj.bind("click", function() {
obj.addClass(o.animation);
obj.addClass('vivify');
},
// this callback is not valid
function() {
obj.removeClass(o.animation);
});
because .bind() accept parameters like following:
.bind(eventName, [eventData], [callback])
Read about .bind()
To remove class you can do:
obj.bind("click", function() {
obj.addClass(o.animation);
obj.addClass('vivify');
// you can removeClass here
obj.removeClass(o.animation);
// but you need some delay
setTimeou(function() {
obj.removeClass(o.animation);
}, 5000);
});
To increase the timeout you can do following:
return this.each(function(index, val) {
var o = options;
var obj = $(this);
var animation = o.animation;
obj.bind("click", function() {
obj.addClass(o.animation);
obj.addClass('vivify');
}, index * 2000);
});

Building simple jQuery plugin, need assistance

I'm building my first ever jQuery plugin (it's just a simple experiment). Here's what I have so far:
(function($){
$.fn.extend({
auchieFader: function(options) {
var defaults = {
mask: '',
topImg : '',
}
var options = $.extend(defaults, options);
return this.each(function() {
var o = options;
var obj = $(this);
var masker = $(o.mask, obj);
masker.hover(function () {
$(o.topImg).stop().animate({
"opacity": "0"
}, "slow");
}, function () {
$(o.topImg).stop().animate({
"opacity": "1"
}, "slow");
});
});
}
});
})(jQuery);
I'm then calling the plugin using:
$('.fader').auchieFader({mask: ".mask", topImg: ".top"});
If I then add another request say:
$('.fader2').auchieFader({mask: ".mask", topImg: ".top"});
Then no matter what instance of my 2 faders I hover both of them will trigger. I know this is because my mask mask and topImg options have the same class - but how can I modify the plugin to allow for these items to have the same class? I know it's probably something really simple, but I'm still finding my way with jQuery and Javascript in general. Any other tips on improving my code would also be greatly appreciated!
Cheers,
Chris
You seem to already have the answer to your question in the code. For the masker you wrote this:
var masker = $(o.mask, obj);
Which scopes the class in o.mask inside of the dom element obj
I think you just need to do the same thing for o.topImg.
Try changing
masker.hover(function () {
$(o.topImg)
into
masker.hover(function () {
$(o.topImg, obj)
try this:
(function($){
$.fn.extend({
auchieFader: function(options) {
var
// defaults options
defaults = {
mask: null,
topImg: null
},
// extend options in defaults
o = $.extend(defaults, options);
if (!o.mask || !o.topImg) return this;
return this.each(function() {
var
masker = $(o.mask, this),
topImg = $(o.topImg, this);
if (masker.length == 0 || topImg.length == 0) return;
masker.hover(
function () { topImg.stop().animate({ "opacity": "0" }, "slow"); },
function () { topImg.stop().animate({ "opacity": "1" }, "slow"); }
);
});
};
});
})(jQuery);

Categories

Resources