I am very new to Javascript.
I am trying to write this baby jQuery plugin that I will use to make dropdown lists. What I am failing to achieve (beyond things that I do not notice) is to neatly exit or deactivate my active instance as I click on another instance. I tried to illustrate my problem in the following fiddle (keeping the structure I am using):
https://jsfiddle.net/andinse/m0kwfj9d/23/
What the Javascript looks like:
$(document).ready(function() {
$.fn.activator = function() {
var Activator = function(el) {
this.html = $('html');
this.el = el;
this.is_active = false;
this.initialize();
};
Activator.prototype.initialize = function() {
var self = this;
self.el.on('click', function(e) {
if (self.is_active === false) {
self.toggle('activate');
} else {
self.toggle('deactivate');
}
});
};
Activator.prototype.toggle = function(action) {
var self = this;
if (action === 'activate') {
console.log('activating ' + self.el[0].className);
self.is_active = true;
self.el.addClass('red');
self.html.on('click', function(e) {
if (e.target != self.el[0]) {
self.toggle('deactivate');
}
});
}
if (action === 'deactivate') {
console.log('deactivating ' + self.el[0].className);
self.is_active = false;
self.el.removeClass('red');
self.html.off('click');
}
};
if (typeof this !== 'undefined') {
var activator = new Activator(this);
}
return this;
};
$('.a').activator();
$('.b').activator();
$('.c').activator();
});
My idea was:
To watch for clicks on html as soon as the instance is active (thus ready to be deactivated). On click, to check if the event.target is the same as the active instance. If not, to deactivate this instance.
To stop watching for clicks as soon as the instance is inactive. So that we're not doing unnecessary work.
When it is set like this, it seems to work for only one cycle (click on A activates A then click on B activates B and deactivates A then click on C activates C but doesn't deactivate B).
If I get rid of the "self.html.off('click')" it seems to work kind of ok but if I look at the log I can see the "toggle" function is sometimes triggered multiple times per click. There must be a cleaner way.
Any piece of help greatly appreciated.
With your logic, when clicking any element you should deactivate any current activated element. Either do it globally:
$('.your_activation_class').removeClass('.your_activation_class');
or in some parent scope
$('some_parent_selector .your_activation_class').removeClass('.your_activation_class');
Related
Working with Wordpress Meta Box and I used the code of Dale Sattler from this How can I use the built in Wordpress “browse link” functionality? to create a custom field with wp browse link it works fine, but it inserted the data in wp-editor too.
I try to prevent the default event using code here Use WordPress link insert dialog in metabox? but doesn't work, I try that code too but it have a bug too.
here is my code
var _link_sideload = false; //used to track whether or not the link dialogue actually existed on this page, ie was wp_editor invoked.
var link_btn = (function($){
'use strict';
var _link_sideload = false; //used to track whether or not the link dialogue actually existed on this page, ie was wp_editor invoked.
var input_field = '';
/* PRIVATE METHODS
-------------------------------------------------------------- */
//add event listeners
function _init() {
$('body').on('click', '.link-btn', function(event) {
_addLinkListeners();
_link_sideload = false;
input_field = $(this).attr('href');
var link_val_container = $(input_field);
if ( typeof wpActiveEditor != 'undefined') {
wpLink.open();
wpLink.textarea = $(link_val_container);
} else {
window.wpActiveEditor = true;
_link_sideload = true;
wpLink.open();
wpLink.textarea = $(link_val_container);
}
return false;
});
}
/* LINK EDITOR EVENT HACKS
-------------------------------------------------------------- */
function _addLinkListeners() {
$('body').on('click', '#wp-link-submit', function(event) {
var linkAtts = wpLink.getAttrs();
console.log(linkAtts);
var link_val_container = $(input_field);
link_val_container.val(linkAtts.href);
_removeLinkListeners();
return false;
});
$('body').on('click', '#wp-link-cancel', function(event) {
_removeLinkListeners();
return false;
});
}
function _removeLinkListeners() {
if(_link_sideload){
if ( typeof wpActiveEditor != 'undefined') {
wpActiveEditor = undefined;
}
}
wpLink.close();
wpLink.textarea = $('html');//focus on document
$('body').off('click', '#wp-link-submit');
$('body').off('click', '#wp-link-cancel');
}
/* PUBLIC ACCESSOR METHODS
-------------------------------------------------------------- */
return {
init: _init,
};
})(jQuery);
please help, please ....
Ok I think I found a way to remove the link from the content. In your submit event you need to add:
$('body').on('click', '#wp-link-submit', function(event) {
var linkAtts = wpLink.getAttrs();
var link_val_container = $(input_field);
link_val_container.val(linkAtts.href);
var $frame = $('#content_ifr'),
$added_links = $frame.contents().find("a[data-mce-href]");
$added_links.each(function(){
if ($(this).attr('href') === linkAtts.href) {
$(this).remove();
}
});
_removeLinkListeners();
return false;
});
$('#content_ifr') is the iframe that loads tinymce editor with content inside. Since the iframe is loaded from the same domain you can mess around it (luckily). So you just go through its contents and you're looking for anchors that have data attribute called mce-href, and if the link that you've just added has the href value as the one you've added it removes them.
I re did this part of the code because I've noticed that all the links in my content had this attribute so you cannot just remove all anchors that have
data-mce-href attribute because that would remove all of them. And you only want to remove those you've added in your metabox.
This did the trick for me :)
I'm stuck with my modal popup plugin since a week.
I'll try to explain as much as i can but first, here is the jsfiddle: http://jsfiddle.net/hideo/yth37hhf/27/
I know the code contains some other functions but they are useful for my plugin.
So, my issue is that the function "triggerLinkAction" contains an addEventListener which is not fired.
(function() {
Window.prototype.triggerLinkAction = function(){
var triggeredLink = document.getElementById("triggeredOtherAction");
var inputTarget = document.getElementById("inputText");
console.log('triggeredLink',triggeredLink);
triggeredLink.addEventListener("click", function (e) {
alert('If this pops out, I will be very happy!!!');
e.preventDefault();
inputTarget.value = "This text should be on the input field...";
}, true);
}
})();
The targeted element is inside the modal, and this modal is displayed by clicking on the link "A small modal".
When the plugin calls ShowModal(), I trigger the TransitionEnd event to call a function
[.... code ...]
function ShowModal() {
vars.popupContainer.classList.add("show");
hsdk.PrefixedEvent(vars.popupOverlay, "TransitionEnd", function (e) {
executeFunctions();
});
}
[.... code ...]
The executeFunctions() will check which functions need to be called:
[.... code ...]
function executeFunctions() {
if (vars.opts && vars.opts.fn) {
var allFunctions = vars.opts.fn.split(',');
for (var i = 0; i < allFunctions.length; i++)
{
var functionName = allFunctions[i];
var functionToExecute = window[functionName];
if(typeof functionToExecute === 'function') {
functionToExecute();
}
}
}
}
[.... code ...]
There are some comments on the javascript part about the plugin, but feel free to ask if I can provide any other information.
PS: I don't care about IE for now ;-)
I asked this question yesterday hopefully this one is clearer as I've now provided a working example of my store.
I'm developing a Shopify Theme. I've been using Timber as my base and I'm currently having a problem with my Quick Cart and Quick Shop/View drawers.
I have 2 drawers on the right of my site, 1 for the cart and 1 for the product quick view option. The drawers currently slide open - #PageContainer moves to the left on click to reveal each drawer.
As they are currently sitting on top of each other I need to alter the JS so that on click the z-index changes so that the correct drawer being called is highest in the stack.
I'm not great with JS so not sure if this is a simple task?
Here is a link to my Dev Store
JS:
timber.Drawers = (function () {
var Drawer = function (id, position, options) {
var defaults = {
close: '.js-drawer-close',
open: '.js-drawer-open-' + position,
openClass: 'js-drawer-open',
dirOpenClass: 'js-drawer-open-' + position
};
this.$nodes = {
parent: $('body, html'),
page: $('#PageContainer'),
moved: $('.is-moved-by-drawer')
};
this.config = $.extend(defaults, options);
this.position = position;
this.$drawer = $('#' + id);
if (!this.$drawer.length) {
return false;
}
this.drawerIsOpen = false;
this.init();
};
Drawer.prototype.init = function () {
$(this.config.open).on('click', $.proxy(this.open, this));
this.$drawer.find(this.config.close).on('click', $.proxy(this.close, this));
};
Drawer.prototype.open = function (evt) {
// Keep track if drawer was opened from a click, or called by another function
var externalCall = false;
// Prevent following href if link is clicked
if (evt) {
evt.preventDefault();
} else {
externalCall = true;
}
// Without this, the drawer opens, the click event bubbles up to $nodes.page
// which closes the drawer.
if (evt && evt.stopPropagation) {
evt.stopPropagation();
// save the source of the click, we'll focus to this on close
this.$activeSource = $(evt.currentTarget);
}
if (this.drawerIsOpen && !externalCall) {
return this.close();
}
// Add is-transitioning class to moved elements on open so drawer can have
// transition for close animation
this.$nodes.moved.addClass('is-transitioning');
this.$drawer.prepareTransition();
this.$nodes.parent.addClass(this.config.openClass + ' ' + this.config.dirOpenClass);
this.drawerIsOpen = true;
// Run function when draw opens if set
if (this.config.onDrawerOpen && typeof(this.config.onDrawerOpen) == 'function') {
if (!externalCall) {
this.config.onDrawerOpen();
}
}
if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
this.$activeSource.attr('aria-expanded', 'true');
}
// Lock scrolling on mobile
this.$nodes.page.on('touchmove.drawer', function () {
return false;
});
this.$nodes.page.on('click.drawer', $.proxy(function () {
this.close();
return false;
}, this));
};
Drawer.prototype.close = function () {
if (!this.drawerIsOpen) { // don't close a closed drawer
return;
}
// deselect any focused form elements
$(document.activeElement).trigger('blur');
// Ensure closing transition is applied to moved elements, like the nav
this.$nodes.moved.prepareTransition({ disableExisting: true });
this.$drawer.prepareTransition({ disableExisting: true });
this.$nodes.parent.removeClass(this.config.dirOpenClass + ' ' + this.config.openClass);
this.drawerIsOpen = false;
this.$nodes.page.off('.drawer');
};
return Drawer;
})();
Update
As instructed by Ciprian I have placed the following in my JS which is making the #CartDrawer have a higher z-index. I'm now unsure how I adapt this so that it knows which one to have higher dependant on which button is clicked. This is what I've tried:
...
Drawer.prototype.init = function () {
$(this.config.open).on('click', $.proxy(this.open, this));
$('.js-drawer-open-right-two').click(function(){
$(this).data('clicked', true);
});
if($('.js-drawer-open-right-two').data('clicked')) {
//clicked element, do-some-stuff
$('#QuickShopDrawer').css('z-index', '999');
} else {
//run function 2
$('#CartDrawer').css('z-index', '999');
}
this.$drawer.find(this.config.close).on('click', $.proxy(this.close, this));
};
...
The approach would be like this:
$('.yourselector').css('z-index', '999');
Add it (and adapt it to your needs) inside your onclick() function.
if you need to modify the z-index of your div when clicking a buton, you shoud put in this code on your onclick() function, else if you need to activate it when you looding the page you shoud put it on a $( document ).ready() function , the code is :
$('#yourID').css('z-index', '10');
You can use:
document.getElementById("your-element-id").style.zIndex = 5;
It's pure Javascript and sets the z-index to 5. Just bind this to onClick event!
I've written a few events to handle opening and closing of a snap js drawer. This code below works, but I feel it could be written more efficiently. Any suggestions?
function openMobileMenu() {
event.preventDefault();
snapper.open('left');
$('#btn-menu').off('click', openMobileMenu);
$('#btn-menu').on('click', closeMobileMenu);
}
function closeMobileMenu() {
event.preventDefault();
snapper.close('left');
$('#btn-menu').on('click', openMobileMenu);
$('#btn-menu').off('click', closeMobileMenu);
}
$('#btn-menu').on('click', openMobileMenu);
Make your code modular and your concepts explicit.
You can start by creating a MobileMenu object which encapsulates the logic.
Note: The following code was not tested.
var MobileMenu = {
_snapper: null,
_$button: null,
_direction: 'left',
init: function (button, snapper, direction) {
this._$button = $(button);
this._snapper = snapper;
if (direction) this._direction = direction;
this._toggleSnapperVisibilityWhenButtonClicked();
},
_toggleSnapperVisibilityWhenbuttonClicked: function () {
this._$button.click($.proxy(this.toggle, this));
},
toggle: function () {
var snapperClosed = this._snapper.state().state == 'closed',
operation = snapperClosed? 'open' : 'closed';
this._snapper[operation](this._direction);
}
};
Then in your page you can just do the following to initialize your feature:
var mobileMenu = Object.create(MobileMenu).init('#btn-menu', snapper);
Modularizing your code will make it more maintainable and understandable in the long run, but also allow you to unit test it. You also gain a lot more flexibily because of the exposed API of your component which allows other code to interact with it.
E.g. you can now toggle the menu visibility with mobileMenu.toggle().
Use a variable to keep track of the state:
var menu_open = false;
$("#btn-menu").on('click', function(event) {
event.preventDefault();
if (menu_open) {
snapper.close('left');
} else {
snapper.open('left');
}
menu_open = !menu_open; // toggle variable
});
snap has a .state() method, which returns an object stuffed with properties, one of which is .state.
I think you want :
$('#btn-menu').on('click', function() {
if(snapper.state().state == "closed") {
snapper.open('left');
} else {
snapper.close('left');
}
});
Or, in one line :
$('#btn-menu').on('click', function() {
snapper[['close','open'][+(snapper.state().state == 'closed')]]('left');
});
Also, check How do I make a toggle button? in the documentation.
I'm trying to find a way to detect changes to the element style but I haven't had much luck. The code below works on a new property I define like tempBgColor but I cannot override/shadow an existing property like color. I know jquery has a watch function, but it only detects changes from the jquery api but not directly changing the value of a style something like elem.style.color.
var e = document.getElementById('element');
e.style.__defineGetter__("color", function() {
return "A property";
});
e.style.__defineSetter__("color", function(val) {
alert("Setting " + val + "!");
});
Any pointers?
You should be able to do this with a MutationObserver - see demo (Webkit only), which is the new, shiny way of getting notified about changes in the DOM. The older, now deprecated, way was Mutation events.
Demo simply logs in the console the old and new values when the paragraph is clicked. Note that the old value will not be available if it was set via a non-inline CSS rule, but the change will still be detected.
HTML
<p id="observable" style="color: red">Lorem ipsum</p>
JavaScript
var MutationObserver = window.WebKitMutationObserver;
var target = document.querySelector('#observable');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log('old', mutation.oldValue);
console.log('new', mutation.target.style.cssText);
});
});
var config = { attributes: true, attributeOldValue: true }
observer.observe(target, config);
// click event to change colour of the thing we are observing
target.addEventListener('click', function(ev) {
observable.style.color = 'green';
return false;
}, false);
Credit to this blog post, for some of the code above.
With Chrome's Developer Tools open, you can find the element whose style's change you're interested in, right click it, select "Break on..." and "Attributes modifications".
here is a naive implementation using setTimeout with undescorejs.
The only way to find out which change was made is to iterate through the style object properties.
Here is the live example
$( function () {
var ele = document.getElementById('ele'),
oldStyle = {};
function checkEquality() {
style = _.clone(ele.style);
if (!_.isEqual(style, oldStyle)) {
console.log('Not equal');
oldStyle = _.clone(style);
} else {
console.log('Equal');
}
_.delay(checkEquality, 2000);
}
checkEquality();
$('a#add_prop').on('click', function () {
var props = $('#prop').val().replace(/ /g, '').split(':');
console.log(props);
$(ele).css(props[0], props[1]);
});
$('#prop').on('keydown', function (e) {
if (e.keyCode == 13) {
$('a#add_prop').trigger('click');
}
});
});