create javascript function to toggle sprite - javascript

I am working on creating a JS/jQuery function to toggle the position of an icon sprite. So far I have gotten this code to work:
$('.toggle-completed').mouseup(function(){
var sp = ($(this).css('background-position'));
$(this).css('background-position',
sp = sp == '-82px 50%'? '-101px 50%' : '-82px 50%'
);
});
However I want to abstract this so that it can work with any sprite pair. Something like this:
var toggleSprite = function(firstPosition, secondPosition){
var sp = ($(this).css('background-position'));
$(this).css('background-position',
sp = sp == firstPosition ? secondPosition : firstPosition
);
};
Which would be called as follows:
toggleSprite('-82px 50%', '-101px 50%');
However this code generates an error:
Uncaught TypeError: Cannot use 'in' operator to search for
'backgroundPosition' in undefined
I am thinking that either 1. I can't really use 'this' in a function. Or perhaps I am not calling the function correctly.
Thank you.

The problem seems to be in the is the this reference... it does not looks like to be referencing the target element.
I think you can write it as a simple plugin like
$.fn.toggleSprite = function (firstPosition, secondPosition) {
return $(this).css('background-position', function(idx, sp){
return sp = sp == firstPosition ? secondPosition : firstPosition;
});
};
then call it like
$(this).toggleSprite('pos1', 'pos2');//or $(el)
or pass the elements as a param to the method
var toggleSprite = function (els, firstPosition, secondPosition) {
$(els).each(function () {
var sp = $(this).css('background-position');
$(this).css('background-position', sp = sp == firstPosition ? secondPosition : firstPosition);
});
};
then
toggleSprite(myels, 'pos1', 'pos2')
A third option is to pass a custom context to the toggle method using Function.call()
toggleSprite('-82px 50%', '-101px 50%').call(myel);

Inside of a jQuery callback, this often refers to the DOM element that triggered the event, so can be used in a selector ($(this)), but in your example that creates an error, the context is a vanilla js function, so this refers to the current scope, not a DOM object.
Perhaps extend the jQuery fn object...
jQuery.fn.toggleSprite = function(firstPosition, secondPosition){
var sp = ($(this).css('background-position'));
$(this).css('background-position',
sp = sp == firstPosition ? secondPosition : firstPosition
);
// also, return $(this) so that chaining is not broken...
return $(this);
};
And call like this...
$('.some.selector').toggleSprite('-82px 50%', '-101px 50%');
So that the function knows what this refers to.

The problem you're having has to do with the fact that the function context this is not bound to a specific DOM element when used in a generic (i.e. non-jQuery) function. One of the more powerful features of the jQuery library is that the function context this is bound to the DOM element that calls the jQuery function, allowing the user to easily target a specific element. In regular JavaScript, this refers to the current scope, which in your case is the function toggleSprite.
For your purposes, I think a simple jQuery plugin function would do the trick:
$.fn.toggleSprite = function(pos1, pos2) {
return $(this).css('background-position', function(_, sp){
return sp == pos1 ? pos2 : pos1;
});
};
The benefit of this method is that you can then call this function on any jQuery object as you normally would:
$('.sprite').toggleSprite('-82px 50%', '-101px 50%');

Related

How to assign a function to a object method in javascript?

I'd like to 'proxy' (not sure if that's the term at all) a function inside a function object for easy calling.
Given the following code
function Soldier() {
this.el = $("<div></div>").addClass('soldier');
this.pos = this.el.position; // $(".soldier").position(), or so I thought
}
In the console:
s = new Soldier();
$("#gamemap").append(s.el); // Add the soldier to the game field
s.pos === s.el.position // this returns true
s.el.position() // Returns Object {top: 0, left: 0}
s.pos() // Returns 'undefined'
What am I doing wrong in this scenario and is there an easy way to achieve my goal (s.pos() to return the result of s.el.position()) ?
I thought about s.pos = function() { return s.el.position(); } but looks a bit ugly and not apropriate. Also I'd like to add more similar functions and the library will become quite big to even load.
When you're calling s.pos(), its this context is lost.
You can simulate this behavior using call():
s.pos.call(s); // same as s.pos()
s.pos.call(s.el); // same as s.el.position()
This code is actually ok:
s.pos = function() { return s.el.position(); }
An alternative is using bind():
s.pos = s.el.position.bind(el);
You can use the prototype, that way the functions will not be created separately for every object:
Soldier.prototype.pos = function(){ return this.el.position(); }
I'd recommend to use the prototype:
Soldier.prototype.pos = function() { return this.el.position(); };
Not ugly at all, and quite performant actually.
If you want to directly assign it in the constructor, you'll need to notice that the this context of a s.pos() invocation would be wrong. You therefore would need to bind it:
…
this.pos = this.el.position.bind(this.el);
It's because the context of execution for position method has changed. If you bind the method to work inside the element context it will work.
JS Fiddle
function Soldier() {
this.el = $("<div></div>").addClass('soldier');
this.pos = this.el.position.bind(this.el);
}
var s = new Soldier();
$("#gamemap").append(s.el);
console.log(s.pos());

(Override || Extend) Javascript Method

I want to change the method DomElement.appendChild() to work differently when applied on an Object that I have created:
// using MooTools;
var domElement = new Class({...});
var MyObject = new domElement(...);
$(document).appendChild(MyObject); // Error
// I want to add to (extend) appendChild, to do something specific, when:
alert(MyObject instanceof domElement); // true
I could do this by modifying the mootools.js, but I want to avoid this at all costs, because I am certain one day, some other developer will overwrite the js file with an updated version, and I will be imprisoned for murder.
Please, keep me out of jail!
I dont know about MooTools, but there is a simple way in native JS to do this..
var orgAppendChild = document.appendChild;
document.appendChild = localAppendHandler;
var domElement = new Class({...});
var MyObject = new domElement(...);
// document.appendChild(MyObject);
var ss = document.createElement('tr');
document.appendChild (ss);
function localAppendHandler(MyObject)
{
if (MyObject instanceof domElement)
UrCustomRoutine(MyObject )
else
orgAppendChild(MyObject);
}
function UrCustomRoutine(MyObject ){
//your custom routine
}
Hope this helps
Update:
From your further explanation (handling appendChild of any DOM element), i understand that there is no generic way of defining local hanlders to trap the event.
But there is a workaround (this is very ugly i know). Means you have to define a LocalHandler for the DOM element before you are goin to use the appendChild .
Before doing this
document.getElementByID('divTarget').appendChild()
you have to reassign the localhandler to the element you need to access
orgAppendChild = document.getElementById('divTarget').appendChild;
document.getElementById('divTarget').appendChild = localAppendHandler;
var sss = document.createElement('table');
document.getElementById('divTarget').appendChild(sss);
Another Alternative:
If you wanted to keep it simple, i suggest this alternate way. Create AppendChild local method which accepts object and the node name as param.
function AppendChild(MyObject ,Node)
{
if (MyObject instanceof domElement)
//your custom routine
else if (Node=="" && (MyObject instanceof domElement==false))
document.appendChild(MyObject);
else if (Node!="" && (MyObject instanceof domElement==false))
eval( "document.getElementById('" + Node + "').appendChild(MyObject);");
}
And
If you wanted to append DOM element to document
AppendChild(DOMelement,"")
If you wanted to append DOM element inside other
container
AppendChild(DOMelement,"divTarget")
If you wanted to process your custom object
AppendChild(customObject,"")
this is the best way to do this.
you ought to look at using object.toElement() in your class
http://blog.kassens.net/toelement-method
then just use document.body.inject(instanceObject);
var originAppend = Element.prototype.appendChild;
Element.prototype.appendChild = function(el){
// do something to el
originAppend.call(this, el);
}

Using 'this' inside a link generated by a javascript object

Javascript is pretty shaky for me, and I can't seem to find the answer to this. I have some code along the lines of
var Scheduler = function(divid,startDate,mode){
this.setHeader = function(){
header.innerHTML = 'Show';
}
this.showScheduler = function period(){
...
}
};
My problem is, how do I put the onclick into the HTML so that it properly calls the showScheduler function for the appropriate instance of the current scheduler object that I'm working with?
I wouldn't do whatever it is you're doing the way you're doing it, but with the code the way you have it, I would do this (lots ofdo and doing :) ):
var Scheduler = function(divid, startDate, mode){
var that = this;
this.setHeader = function(){
header.innerHTML = 'Show';
header.firstChild.onclick = function() { that.showScheduler(1); };
}
this.showScheduler = function period(){
...
}
};
You should use a framework for this type of thing. If you don't use one then you gotta declare each instance of schedular as a global object, and you will need the name of the instance in order to call it from the link. Look at the following link
http://developer.yahoo.com/yui/examples/event/eventsimple.html
They only show a function being applied, but you can also do something like this
YAHOO.util.Event.addListener(myAnchorDom, "click", this.showScheduler,this,true);
Where myAnchorDom is the achor tag dom object. This will have showScheduler function execute within the scope of your scheduler object.
Instead of working with innerHTML use the DOM methods.
Try replacing this:
header.innerHTML = 'Show';
with this:
var x = this; // create a closure reference
var anchor = document.createElement('a');
anchor.href= '#';
anchor.innerHTML = 'Show';
anchor.onclick = function() { x.showScheduler(1); }; //don't use onclick in real life, use some real event binding from a library
header.appendChild(anchor);
Explanation:
The "this" in the original code refers to the element which fired the event, i.e. the anchor ("this' is notoriously problematic for things like, well, like this). The solution is to create a closure on the correct method (which is why you have to create something like the var x above) which then only leaves the problem of passing in the parameter which is accomplished by wrapping the method in another function.
Strictly speaking it would be much preferable to bind eventhandlers with the addEventListener/attachEvent pair (because direct event assignment precludes the ability to assign multiple handlers to one event) but it's best handled using a library like jquery if you're new to JS anyway.
You can add an event handler to the header object directly:
var me = this;
this.setHeader = function(){
header.innerHTML = 'Show';
header.addHandler("click", function(e) { me.showScheduler(1); });
}
Insite the passed function, this will refer to the header element.
var Scheduler = function(divid, startDate, mode)
{
var xthis = this;
this.setHeader = function()
{
var lnk = document.createElement("a");
lnk.addEventListener("click", xthis.showScheduler, false);
lnk.innerText = "Show";
lnk.setAttribute('href', "#");
header.appendChild(lnk);
}
this.showScheduler = function period(){
...
}
};
When using "this" inside the onclick attribute, you're actually referring to the anchor tag object. Try this and see if it works:
this.setHeader = function(){
header.innerHTML = 'Show';
}

call function inside a nested jquery plugin

There are many topics related to my question and i have been through most of them, but i haven't got it right. The closest post to my question is the following:
How to call functions that are nested inside a JQuery Plugin?
Below is the jquery plugin i am using. On resize, the element sizes are recalculated. I am now trying to call the function resizeBind() from outside of the jquery plugin and it gives me error
I tried the following combinations to call the function
$.fn.splitter().resizeBind()
$.fn.splitter.resizeBind()
Any ideas, where i am getting wrong?
;(function($){
$.fn.splitter = function(args){
//Other functions ......
$(window).bind("resize", function(){
resizeBind();
});
function resizeBind(){
var top = splitter.offset().top;
var wh = $(window).height();
var ww = $(window).width();
var sh = 0; // scrollbar height
if (ww <0 && !jQuery.browser.msie )
sh = 17;
var footer = parseInt($("#footer").css("height")) || 26;
splitter.css("height", wh-top-footer-sh+"px");
$("#tabsRight").css("height", splitter.height()-30+"px");
$(".contentTabs").css("height", splitter.height()-70+"px");
}
return this.each(function() {
});
};
})(jQuery);
I had the same problem. Those answers on related posts didn't work for my case either. I solved it in a round about way using events.
The example below demonstrates calling a function that multiplies three internal data values by a given multiplier, and returns the result. To call the function, you trigger an event. The handler in turn triggers another event that contains the result. You need to set up a listener for the result event.
Here's the plugin - mostly standard jQuery plugin architecture created by an online wizard:
(function($){
$.foo = function(el, options){
// To avoid scope issues, use 'base' instead of 'this'
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("foo", base);
base.init = function(){
base.options = $.extend({},$.foo.defaultOptions, options);
// create private data and copy in the options hash
base.private_obj = {};
base.private_obj.value1 = (base.options.opt1);
base.private_obj.value2 = (base.options.opt2);
base.private_obj.value3 = (base.options.opt3);
// make a little element to dump the results into
var ui_element = $('<p>').attr("id","my_paragraph").html(base.private_obj.value1 +" "+ base.private_obj.value2+" " +base.private_obj.value3);
base.$el.append(ui_element);
// this is the handler for the 'get_multiplied_data_please' event.
base.$el.bind('get_multiplied_data_please', function(e,mult) {
bar = {};
bar.v1 = base.private_obj.value1 *mult;
bar.v2 = base.private_obj.value2 *mult;
bar.v3 = base.private_obj.value3 *mult;
base.$el.trigger("here_is_the_multiplied_data", bar);
});
};
base.init();
}
$.foo.defaultOptions = {
opt1: 150,
opt2: 30,
opt3: 100
};
$.fn.foo = function(options){
return this.each(function(){
(new $.foo(this, options));
});
};
})(jQuery);
So, you can attach the object to an element as usual when the document is ready. And at the same time set up a handler for the result event.
$(document).ready(function(){
$('body').foo();
$('body').live('here_is_the_multiplied_data', function(e, data){
console.log("val1:" +data.v1);
console.log("val2:" +data.v2);
console.log("val3:" +data.v3);
$("#my_paragraph").html(data.v1 +" "+ data.v2+" " +data.v3);
});
})
All that's left is to trigger the event and pass it a multiplier value
You could type this into the console - or trigger it from a button that picks out the multiplier from another UI element
$('body').trigger('get_multiplied_data_please', 7);
Disclaimer ;) - I'm quite new to jQuery - sorry if this is using a hammer to crack a nut.
resizeBind function is defined as private so you cannot access it from outside of it's scope. If you want to use it in other scopes you need to define it like that
$.fn.resizeBind = function() { ... }
Then you would call it like that $(selector').resizeBind()
You have defined the resizeBind function in a scope that is different from the global scope. If you dont'use another javascript framework or anything else that uses the $ function (to prevent conflict) you can delete the
(function($){
...
})(jQuery);
statement and in this way the function will be callable everywhere without errors
I didn't test it:
this.resizeBind = function() { .... }

Is it possible to listen to a "style change" event?

Is it possible to create an event listener in jQuery that can be bound to any style changes? For example, if I want to "do" something when an element changes dimensions, or any other changes in the style attribute I could do:
$('div').bind('style', function() {
console.log($(this).css('height'));
});
$('div').height(100); // yields '100'
It would be really useful.
Any ideas?
UPDATE
Sorry for answering this myself, but I wrote a neat solution that might fit someone else:
(function() {
var ev = new $.Event('style'),
orig = $.fn.css;
$.fn.css = function() {
$(this).trigger(ev);
return orig.apply(this, arguments);
}
})();
This will temporary override the internal prototype.css method and the redefine it with a trigger at the end. So it works like this:
$('p').bind('style', function(e) {
console.log( $(this).attr('style') );
});
$('p').width(100);
$('p').css('color','red');
Things have moved on a bit since the question was asked - it is now possible to use a MutationObserver to detect changes in the 'style' attribute of an element, no jQuery required:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutationRecord) {
console.log('style changed!');
});
});
var target = document.getElementById('myId');
observer.observe(target, { attributes : true, attributeFilter : ['style'] });
The argument that gets passed to the callback function is a MutationRecord object that lets you get hold of the old and new style values.
Support is good in modern browsers including IE 11+.
Since jQuery is open-source, I would guess that you could tweak the css function to call a function of your choice every time it is invoked (passing the jQuery object). Of course, you'll want to scour the jQuery code to make sure there is nothing else it uses internally to set CSS properties. Ideally, you'd want to write a separate plugin for jQuery so that it does not interfere with the jQuery library itself, but you'll have to decide whether or not that is feasible for your project.
The declaration of your event object has to be inside your new css function. Otherwise the event can only be fired once.
(function() {
orig = $.fn.css;
$.fn.css = function() {
var ev = new $.Event('style');
orig.apply(this, arguments);
$(this).trigger(ev);
}
})();
I think the best answer if from Mike in the case you can't launch your event because is not from your code. But I get some errors when I used it. So I write a new answer for show you the code that I use.
Extension
// Extends functionality of ".css()"
// This could be renamed if you'd like (i.e. "$.fn.cssWithListener = func ...")
(function() {
orig = $.fn.css;
$.fn.css = function() {
var result = orig.apply(this, arguments);
$(this).trigger('stylechanged');
return result;
}
})();
Usage
// Add listener
$('element').on('stylechanged', function () {
console.log('css changed');
});
// Perform change
$('element').css('background', 'red');
I got error because var ev = new $.Event('style'); Something like style was not defined in HtmlDiv.. I removed it, and I launch now $(this).trigger("stylechanged"). Another problem was that Mike didn't return the resulto of $(css, ..) then It can make problems in some cases. So I get the result and return it. Now works ^^ In every css change include from some libs that I can't modify and trigger an event.
As others have suggested, if you have control over whatever code is changing the style of the element you could fire a custom event when you change the element's height:
$('#blah').bind('height-changed',function(){...});
...
$('#blah').css({height:'100px'});
$('#blah').trigger('height-changed');
Otherwise, although pretty resource-intensive, you could set a timer to periodically check for changes to the element's height...
There is no inbuilt support for the style change event in jQuery or in java script. But jQuery supports to create custom event and listen to it but every time there is a change, you should have a way to trigger it on yourself. So it will not be a complete solution.
Interesting question. The problem is that height() does not accept a callback, so you wouldn't be able to fire up a callback. Use either animate() or css() to set the height and then trigger the custom event in the callback. Here is an example using animate() , tested and works (demo), as a proof of concept :
$('#test').bind('style', function() {
alert($(this).css('height'));
});
$('#test').animate({height: 100},function(){
$(this).trigger('style');
});
you can try Jquery plugin , it trigger events when css is change and its easy to use
http://meetselva.github.io/#gist-section-attrchangeExtension
$([selector]).attrchange({
trackValues: true,
callback: function (e) {
//console.log( '<p>Attribute <b>' + e.attributeName +'</b> changed from <b>' + e.oldValue +'</b> to <b>' + e.newValue +'</b></p>');
//event.attributeName - Attribute Name
//event.oldValue - Prev Value
//event.newValue - New Value
}
});
Just adding and formalizing #David 's solution from above:
Note that jQuery functions are chainable and return 'this' so that multiple invocations can be called one after the other (e.g $container.css("overflow", "hidden").css("outline", 0);).
So the improved code should be:
(function() {
var ev = new $.Event('style'),
orig = $.fn.css;
$.fn.css = function() {
var ret = orig.apply(this, arguments);
$(this).trigger(ev);
return ret; // must include this
}
})();
How about jQuery cssHooks?
Maybe I do not understand the question, but what you are searching for is easily done with cssHooks, without changing css() function.
copy from documentation:
(function( $ ) {
// First, check to see if cssHooks are supported
if ( !$.cssHooks ) {
// If not, output an error message
throw( new Error( "jQuery 1.4.3 or above is required for this plugin to work" ) );
}
// Wrap in a document ready call, because jQuery writes
// cssHooks at this time and will blow away your functions
// if they exist.
$(function () {
$.cssHooks[ "someCSSProp" ] = {
get: function( elem, computed, extra ) {
// Handle getting the CSS property
},
set: function( elem, value ) {
// Handle setting the CSS value
}
};
});
})( jQuery );
https://api.jquery.com/jQuery.cssHooks/
I had the same problem, so I wrote this. It works rather well. Looks great if you mix it with some CSS transitions.
function toggle_visibility(id) {
var e = document.getElementById("mjwelcome");
if(e.style.height == '')
e.style.height = '0px';
else
e.style.height = '';
}

Categories

Resources