Updated global variable not being passed to function inside a variable - javascript

So I have these:
var containerId = 'anim1'
var animation = bodymovin.loadAnimation({
container: document.getElementById(containerId),
renderer: 'svg',
loop: false,
autoplay: false,
path: "https://assets10.lottiefiles.com/packages/lf20_rd4wrn81.json"
});
function playanim() {
animation.goToAndPlay(0);
}
$('div').on('click', function() {
containerId = $(this).attr('id');
$('#text').text(containerId + ' was just clicked');
playanim();
})
The global variable containerId is being updated on click events, however the updated value is not being passed to the same containerId within the variable animation. Not sure if it's a bodymovin specific issue- how can I get this to work?

In the line that begins var animation = ..., you are creating an object, setting its container property to document.getElementById(containerId) (using whatever value containerId has at that time, i.e. 'anim1') and then passing that object to bodymovin.loadAnimation (presumably as a configuration - I'm unfamiliar w/that library)
When the div is clicked, the value of containerId is being updated, but this is not changing the value of the container property in the config object you created earlier; that's still equal to anim1. And even if it did change, it might not make any difference unless bodymovin is monitoring that config object. It's quite possible that bodymovin.loadAnimation just checks the value of container when it's initialized, and then never checks it again.
It might achieve what you want if you rebuild the animation every time the div is clicked, e.g. something like this (you might want to stop the animation in the old div as well?)
var animation // just declare it for now, assign below
// The following function will load the animation to the specified container
function setAnimation (containerId) {
console.log ("Loading animation into " + containerId)
animation = bodymovin.loadAnimation({
container: document.getElementById(containerId),
renderer: 'svg',
loop: false,
autoplay: false,
path: "https://assets10.lottiefiles.com/packages/lf20_rd4wrn81.json"
});
}
// Load it in 'anim1' first
setAnimation ('anim1')
function playanim() {
animation.goToAndPlay(0);
}
$('div').on('click', function() {
var id = $(this).attr('id')
console.log(id + ' was just clicked');
$('#text').text(id + ' was just clicked');
setAnimation (id); // re-initialize the animation
animation.addEventListener('data_ready',playanim);
})
Without knowing more about bodymovin (sorry), I can't tell if this is a wildly inefficient way to do it. Maybe bodymovin has a more lightweight way of changing the container for an animation that's already loaded, in which case you could just do that in your click handler.

Related

Javascript force recalculate value of object property

I would like to re-use an "object", however, one of the object's properties values should be recalculated every time the object is accessed.
In my code I have a library which can basically make a list of card views from a data url. This list of card views is added to a page. There are two types of lists: Active Buildings list and Archived Buildings list. Switching between these two lists is done by pressing a button, which triggers the "rerender" function of the repeater shown below.
Archived Buildings should not be clickable. I pass along some configuration options to my library where I handle the relevant parts. However, because of the way I invoke the card view library, the value of the enableClick configuration option is always set to what the state was like at the load of the page.
Example of how the code looks:
$(function () {
var buildingsContainer = $('#buildings');
buildingsContainer.repeater({
url: function () {
var activeFilter = buildingFilter.find('.btn-primary').data('status');
return '/Building/All?status=' + activeFilter;
},
renderItem: cardTemplates(buildingsContainer).building({
activateBuildingUrl: '#(Url.Action("ActivateBuilding", "Building"))/{Id}',
editUrl: '#(Url.Action("Edit", "Building"))/{Id}',
deleteBuildingUrl: '#(Url.Action("SoftDeleteBuilding", "Building"))/{Id}',
enableClick: getActiveFilter() === 'Active'
})
})
});
function getActiveFilter() {
var buildingFilter = $('#buildingFilter');
return buildingFilter.find('.btn-primary').data('status');
}
No matter what the currently pressed button is, enableClick is always set to what it was when the page opened.
To better demonstrate my problem, I have created a JSFiddle:
https://jsfiddle.net/e3xnbxov/
In this JSFiddle, you see I have a options object with a value property. In the button's click listeners I print this value. However, it always remains on Active, even though I switch between Active and Archived. How can I make it so the value of the property is recalculated?
I think you have 2 options here.
1) Set the property as a function, and evaluate it:
$(function() {
var options = {
value: ()=>$('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
jsfiddle: https://jsfiddle.net/mw8kuq6L/
2) Just use "this" to directly access the data value you want to check:
$(function() {
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log($(this).data('status'));
});
});
The problem is that the object (options) is created once, and the property is set once.
At the moment that the creation (and property setting) occurs, the 'active' button matches the jQuery selector ($('#container').find('.btn-primary')).
Javascript, like many languages, uses references. When you set the object's property, it received a reference to the result of the jQuery selector, not the selector (as a method) itself.
You could change it to behave more as you're expecting by creating a method on your object:
$(function() {
var options = {
value: function () {
return $('#container').find('.btn-primary').data('status')
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
Thus your options object now has a callable method which dynamically returns what you were expecting.
Otherwise I'd update the property when the selected button changes:
$(function() {
var options = {
value: $('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
options.value = $('#container').find('.btn-primary').data('status');
console.log(options.value);
});
});
This is just meant to be an addition to lpg's answer.
Another way would be to use a getter function which behaves like lpg's value function but can be used like a normal property:
$(function() {
var options = {
// define a getter for the property 'value'
get value () {
return $('#container').find('.btn-primary').data('status');
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value); // use the property for the property 'value'
});
});
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<div id="container">
<button class="btn btn-sm btn-primary" data-status="Active">Active</button>
<button class="btn btn-sm btn-default" data-status="Archived">Archived</button>
</div>

Mutation Observer or DOMNodeInserted

I have a script where I´ve use on the first slide of Adobe Captivate, to automate the task ok creating, courses, the script create the UX, navigation elements, intro/end motions, a game, insert spritesheets with characters, etc...
I´ve used DOMNodeInserted until know to check the modifications on the slide, when the user go to the next slide, the elements are added to the DOM and the page content is changed I´ve used this timer until now to call the function:
function detectChange(){
var slideName = document.getElementById('div_Slide')
slideName.addEventListener("DOMNodeInserted", detectChange, false);
updateSlideElements();
setTimeout(updateSlideElements, 100);
}
So I´m trying to use mutation Observer now:
var observer = new MutationObserver(function(mutations, observer) {
updateSlideElements();
});
observer.observe(document.getElementById('div_Slide').firstChild, {
attributes: true,
childList:true
});
But this is what´s happening, before with setTimeout I could reach the following element:
var motionText2 = document.querySelectorAll('div[id*=motion][class=cp-accessibility]');
This element is the firstChild of:
And the element can be found:
But now with mutationObserver the console returns empty:
I´ve just use a setTimeout inside the observer and watch the parent container not the firstChild:
var observer = new MutationObserver(function(mutations, observer) {
setTimeout(updateSlideElements, 100);
});
observer.observe(document.getElementById('div_Slide'), {
attributes: true,
childList:true
//subtree:true
});

Function is not defined, parameter formatting

I'm trying to create my own lightbox script where I can pass the variables (title, description, itemtype, itemid, etc.) in clean formatting like this (inspired by fancybox):
myFunction({
title: "My title",
description: "My description"
});
Clicking on a certain element prepends some HTML to a div with jQuery.
I have adapted a piece of code I found on Stackoverflow and "kind of" understand the code. The top function has not been changed and worked before I edited the bottom code, to that I added click(function() { } because in the example the code was executed on pageload.
However, when I click my H1 element the firebug console tells me ReferenceError: popup is not defined
This is my Javascript:
$(document).ready(function() {
(function ($) {
$.fn.popup = function (options) {
var settings = $.extend({
title: function (someData) {
return someData;
},
description: function (someData) {
return someData;
},
}, options);
$("#content").prepend(
"<div style=\"position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;\">\
<h1>"+ settings.title +"</h1>\
<p>" + settings.description +"</p>\
</div>"
);
};
}(jQuery));
$(".openbox1").click(function() {
popup({
title: "Title 1",
description: "Description 1"
});
}));
$(".openbox2").click(function() {
popup({
title: "Title 2",
description: "Description 2"
});
}));
});
This is my HTML
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>
A. Wolff commented that I need to execute the function like this:
$(".openbox1").click(function() {
$(this).popup({
...
});
});
This fixed it, thanks!
First off, what you did, and I hope this helps:
// This, of course is same as "document.onload"
// Don't confuse it with "window.onload"
// wich will wait till WHOLE dom is loaded to run any script
$(document).ready(function() {
(function ($) {
// This is, in essence, the start of a jQuery plugin
// This is often referred to as the "quick and dirty setup"
// as it's a direct call to add a method to jQuery's
// element object. Meaning it can be recalled as
// $(element).popup().
// This should not be confused with $.popup = function
// which would just add a method to jQuery's core object
$.fn.popup = function (options) {
var settings = $.extend({
...
}(jQuery));
$(".openbox1").click(function() {
// here is where your issue comes in
// as previously noted, you did not create a
// method named "popup".
// you added a method to jQuery's Element Object
// called "popup".
// This is why `$(this).popup` works and
// plain `popup` does not.
// You're inside an "event" asigned to any element
// having class name `openbox1`. Thus, any call
// in here to `this`, will reference that element
popup({
Secondly, a different example of how to write it. I won't say better because, even if I say my way is better, it wouldn't make your "corrected" way wrong. In Javascript, as the old saying goes, There's more than one way to skin a cat.
My Example:
// Notice I'm adding this plugin BEFORE the document load.
// This means, you could easily add this to a file and load it
// in script tags like any other Javascript,
// as long as it's loaded AFTER jquery.
(function($) {
// this ensures that your plugin name is available and not previously added to jQuery library
if (!$.popup) {
// this also provides us "variable scope" within to work in
// here begin adding the plugin to jQuery
// I started with $.extend, so it can be added to the jQuery library and used in traditional format
// $.popup('element selector', { options })
// as well as the element.action format we'll add later
// $.(element selector).popup({ options })
// This should help give you a good idea of the whole of what all is going on
$.extend({
popup: function() {
var ele = arguments[0], // this is our jQuery element
args = Array.prototype.slice.call(arguments, 1); // this gets the rest of the arguments
// this next step is useful if you make the traditional call `$.popup(this, { options })`
if (!(ele instanceof jQuery)) ele = $(ele);
// now we have total control! Bwahahha!
// Fun aside, here is where it's good to check if you've already asigned this plugin
// if not, then make some "marker", so you can recall the element plugin and comment an
// action instead of reinitializing it
if (!ele.data('popup')) $.popup.init(ele, args);
else {
// at this point, you would know the element already has this plugin initialized
// so here you could change an initial options
// like how with jQueryUI, you might would call:
// $(element).popup('option', 'optionName', value)
}
return ele;
}
});
// here is where we add the $(element selector).popup method
// this simply adds the method to the element object
// If you don't fully understand what's going on inside (as I explain below),
// just know that it's some "fancy footwork" to pass the method onto our initial
// method creation, $.popup
$.fn.extend({
popup: function(/*no need for parameter names here as arguments are evaluated inside and passed on to initial method*/) {
// set this element as first argument to fit with initial plugin method
var args = [$(this)];
// if there are arguments/params/options/commands too be set, add them
if (arguments.length) for (x in arguments) args.push(arguments[x]);
// pass through jquery and our arguments, end result provides same arguments as if the call was:
// $.popup($(element), options)
return $.popup.apply($, args);
}
});
// This next part is not seen in many plugins but useful depending on what you're creating
$.popup.init = function(ele, opt) {
// here is where we'll handle the "heavy work" of establishing a plugin on this element
// Start with setting the options for this plugin.
// This means extending the default options to use any passed in options
// In the most simple of cases, options are passed in as an Oject.
// However, that's not always the case, thus the reason for this being
// a continued array of our arguments from earlier.
// We'll stick with the simplest case for now, your case, that the only options are an
// Object that was passed in.
// using the extend method, with true, with a blank object,
// allows us to added the new options "on top" of the default ones, without changing the default ones
// oh and the "true" part just tells extend to "dig deep" basically (multideminsional)
if (opt && typeof opt[0] == 'object') opt = $.extend(true, {}, $.popup.defaults, opt[0]);
var par = opt.parent instanceof jQuery ? opt.parent : $('body'),
tit = opt.title,
des = opt.description,
// this last one will be the wrapper element we put everything in
// you have this in yours, but it's written in a very long way
// this is jQuery simplified
wrap = $('<div />', { style: 'position:fixed;top:0;left:0;width:100%;height:100%;background:#FFFFFF;' }),
// much like the previous element, cept this is where our title goes
head = $('<h1 />', { text: tit }).appendTo(wrap),
content = $('<p />', { text: des }).appendTo(wrap);
$(par).append(wrap);
// finally, add our marker i mentioned earlier
ele.data('popup', opt);
// just adding the following cause i noticed there is no close
// fyi, i would change this plugin a little and make an actial "open" command, but that's another tutorial
var closer = $('<span />', { text: '[x]', style: 'cursor:pointer;position:absolute;bottom:1em;right:1em;' });
wrap.append(closer);
closer.click(function(e) { ele.data('popup', false); wrap.remove(); });
};
$.popup.defaults = { // establish base properties here that can be over-written via .props, but their values should never truly change
'parent': undefined, // added this to keep it dynamic, instead of always looking for an element ID'd as content
title: '',
description: ''
};
}
})(jQuery);
// the following is basically jQuery shorthand for document.ready
$(function() {
// i think you get the rest
$(".openbox1").on('click', function(e) {
$(this).popup({
title: "Title 1",
description: "Description 1",
parent: $("#content")
});
})
$(".openbox2").on('click', function(e) {
$(this).popup({
title: "Title 2",
description: "Description 2",
parent: $("#content")
});
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="content">
<h1 class="openbox1">open box 1</h1>
<h1 class="openbox2">open box 2</h1>
</div>

Options not cleared from previous call jQuery

I've made this little plugin to show Twitter style alerts:
jQuery.fn.myAlert = function (options) {
var defaults = {
message: false,
class: 'normal',
timer: 500,
delay: 3000,
};
var options = $.extend(defaults, options);
if (!options.message || options.message == '') {
return false;
}
$('body').append('<div id="alert_notification" class="'+options.class+'"><div id="alert_message">' + options.message + '</div></div>')
$('#alert_notification').slideToggle(options.timer).delay(options.delay).slideToggle(options.timer);
return;
}
The problem is: first time it works great, but when I call again in the same page, the values are set from previous call.
Any ideas how to make it work?
My guess is that the slideToggle() isn't showing the <div> you are intending it to show because jQuery isn't appending new <div>s that don't have unique IDs. You should probably remove the originally appended <div>s from the page before appending the new ones. Or, make jQuery replace the existing ones.
$('#alert_notification').remove();
$('body').append(...);
In addition, class is a reserved word in JavaScript. I would change your defaults to:
var defaults = {
message: false,
'class': 'normal',
timer: 500,
delay: 3000,
};
And then access it with options['class'] instead of options.class, or pick a different name for the class property.

Mootools class - storing reference to document body in a class property

Greetings all! I'm trying to learn Mootools classes. I've made this class to add a div to the page.
var F = new Class({
Implements: [Options, Events],
options: {
container: document.body,
width: '250px',
background: '#ccc'
},
initialize: function(options) {
this.setOptions(options);
this.addDemoDiv();
},
addDemoDiv: function() {
var dDiv = new Element('div', {
'class': 'myClass',
html: 'Click me!',
styles: {
padding: '20px',
border: '1px solid #999',
width: this.options.width,
background: this.options.background
},
events: {
click: this.animate
}
});
dDiv.inject(this.options.container);
},
animate: function() {
alert('Hello world');
}
});
window.addEvent('domready', function() {
var item = new F();
});
It's supposed to allow the user to specify the container to inject the div into, with the document body being the default. When I do it like above, the code validates OK, but the script fails to add the div - Firebug and Chrome complain about the container being null or undefined.
I have to change dDiv.inject(this.options.container); to this
if (!this.container) {
dDiv.inject(document.body);
} else {
dDiv.inject(this.container);
}
to make it work.
Can any wise Mootools ninja tell me why inject works when I pass document.body in directly, but breaks when I try to pass it a reference to document.body supposedly stored in the class's container option? I've tried variations on document.body, like 'document.body' and $$('document.body') and $$(document.body).
My guess is that document.body is not available when your class code gets interpreted, this usually occurs when your script is placed in the <head> tags. Moving your script(s) to the bottom of the document (just before </body>) solves a lot and is good practise since your script(s) won't block HTML rendering anymore either.
It's also better to avoid putting a static default DOM references in your class as their availability is always questionable. You can keep options.container null and change your method to:
... code ...
dDiv.inject( this.options.container || document.body );
... code ...
So if this.options.container is not set (falsy) it will default to document.body, this way you can also keep your script(s) in the <head> if you really want to.

Categories