I'm working on an app that already renders a calender using fullcalendar, whenever the page refreshes the time slots are always rendered with the correct colors using the event render callback. It also correctly changes the color of the time slot when the event is clicked upon, using the event click callback.
This is all nice, however I'm trying to programmatically manipulate the renderings of the time slots based on some other stuff the user does after the calendar has fully loaded. In code this is what i'm trying to do
var eventClick = function(calEvent, jsEvent, view) {
// first we change the color of the event to indicate that it was clicked
calEvent.backgroundColor = orangeColor;
calEvent.textColor= darkGreyColor;
calEvent.active=true;
$(this).css('background', orangeColor).css('color', darkGreyColor);
// i cache both the calEvent and the element to manipulate them later on
cachedActiveEvents.push(calEvent);
// here i'm storing this div: <div class="fc-event fc-event-vert ..>
// <div class="fc-event-inner">
// <div class="fc-event-time">12:30 - 1:20</div>
// <div class="fc-event-title">event title</div>
// ..
cachedActiveEventViews.push($($(jsEvent.target).parent().parent()));
..
once clicked, the program displays a modal form that the user fills. Upon completion and dismissal of the dialog, the clicked event needs to change its color to reflect a change of status, that's where i call this method:
function hideLessonPlan() {
..
$.map(cachedActiveEvents, function(calEvent, eventIndex) {
var element = cachedActiveEventViews[eventIndex];
calEvent.backgroundColor = blackColor;
calEvent.textColor = "white"
element.css('background', blackColor).css('color','white');
});
}
This simply don't work. Using Chrome dev tool breakpoints i ensured that the function hideLessonPlan actually talks to the right variables.
Upon further investigation i realized that in both event render and event click callbacks.. the function updateEvent(event) is called after the background properties have been set.. however in my case.. this event is not called after setting the properties. So my question is more of: how can I actually call this upateEvent method from the outside? It seems like a private variable.
update: i made it work but simply by storing the css properties of the selected div and then using jquery to find the same div and highlighting it manually afterwords. not too happy about this hacky way.. hoping someone would come along and show me how to do it through fullcalendar.
it turns out that i simply had to update the property of the calendarEvent ie
calEvent.isActive = true;
calEvent.status = 0; // ie in progress
then call update event:
$('#calendar').fullCalendar('updateEvent',calEvent);
that took care of all the rendering for me
Related
I have a Chrome extension that intercepts and checks tweets before they get posted. To do this, I've add an event listener to the Tweet button. Sine the content is dynamic, I use the solution proposed in this thread:
initialize : function() {
let that = this;
let jsInitChecktimer = setInterval(checkForJsFinished, 111);
function checkForJsFinished () {
if (document.querySelector("div[data-testid='tweetButtonInline']")) {
clearInterval (jsInitChecktimer);
console.log("Button found");
that.addSubmitNewTweetClickHandler();
}
}
},
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.addEventListener('click', function(e) {
console.log("CLICK");
// Stop default event from happening
e.preventDefault();
e.stopImmediatePropagation();
// Do stuff
});
},
If the tweet passed the checks alright, it gets submitted by programmatically triggering the event using .trigger('click').
This works fine, but only once. After a tweet has been submitted and posted, the event listener on the Tweet button is gone, and I cannot intercept the next tweet to check it. I've tried calling initialize() after submitted again -- maybe the button gets removed and newly added to the DOM (it actually disappears fire a moment when submitting a tweet) -- but the querySelector finds the button immediately. But even after calling initialize() again, no click even on the Tweet button fires.
What could be the issue here? My problem is that I don't even know where to look for and how to debug this.
After many more hours, I've finally figured it out. The problem was essentially the highly dynamic content of the new Twitter website. After submitting a tweet, the Tweet button gets indeed removed and added again. In needed to do a serious of changes:
Use a MutationObserver to keep track of any changes. Every time there's a change, call the initialize() function. To avoid too many calls, I do this in case of certain changes (unnecessary detail here)
Change the addSubmitNewTweetClickHandler() method so that the event listener first gets removed in order to avoid duplicate listeners (please note that I use objects hence the use of this compared to my original question)
addSubmitNewTweetClickHandler : function() {
let that = this;
let buttonSubmitTweet = document.querySelector("div[data-testid='tweetButtonInline']");
buttonSubmitTweet.removeEventListener('click', this.handleSubmitNewTweetClick );
this.handleSubmitNewTweetClick = this.handleSubmitNewTweetClick.bind(this)
buttonSubmitTweet.addEventListener('click', this.handleSubmitNewTweetClick );
},
This change required to create the reference function handleSubmitNewTweetClick
Overall, it's still not a perfect solution since I call initialize() many unnecessary time. However, I failed to reliably identify when the Tweet button was added to the document. When I used the MutationObserver none of the added nodes had the attribute data-testid which I need to identify the correct button. I have node idea why this attribute was not there. Maybe the attribute is added some times after added to button, but even with an additional MutationObserver looking for attribute changes I could detect this.
Anyway, it works now and it's only for a prototype.
I have got a javascript based menu generator that is from an external source. The menu functionality occurs via a click event bound to a specific ID... pretty standard.
What I want to do is perform another action on the DOM AFTER that click event fires.
I would rather not hack the code that came in the menu package.
I tried attaching my own click handler - but there isn't an obvious way of ensuring my click handler fires AFTER the menu system's handler. One approach I considered was basing an event on the addition/removal of a class that occurs with the menu system's click handler. But - I quickly found that there is no event based on the change of class on an element (that I know of).
Any ideas? (without hacking the original menu system's js)
You can use the javascirpt Mutation Observer, here is a great article about the subject : Listening to the DOM changes with MutationObserver
OR in an old fashion
If I understand, (1) you can add your own click event listener, (2) you want execute your code after a class name change on the menu element.
In that case, you can use a setInterval to check if the class has changed and if so, trigger your action.
Something like that :
myOnClickFunction(){
var menu = document.querySelector('#MyMenuID');
var timer = setInterval(() => {
if(menu.classList.contains('TheClassNameYouWantToCheck')){
//Clear the interval
clearInterval(timer);
//Execute your actions here
}
}, 50);
//You can also add a maximum checking time
//after 5 seconds the function stop checking for changes
setTimeout(()=>clearInterval(timer), 5000);
}
I am using the ng2-ckeditor plugin. Using the documented way to set the focus (ie on startup) does not work, since I am using *ngIf to show the editor when the user clicks a button.
this.ckConfig = {
uiColor: '#F0F3F4',
height: '350',
extraPlugins: 'divarea',
startupFocus: true
};
So the startupFocus config option does not work, obviously, since the ckeditor is not actually in the DOM at this point.
I also tried using [hidden] since then the editor is in the DOM when my Component initializes, but of course the editor is not visible so it cannot receive focus anyway.
Then I discovered I can fire a ready event when the editor is actually visible and ready for user interaction, like so:
<div *ngIf="isEditMode">
<ckeditor id="ckeditor"
[(ngModel)]="letterhead"
[config]="ckConfig"
(ready)="onReady($event)"
debounce="500">
</ckeditor>
</div>
but how, in the OnReady event, can I set the focus?
EDIT
After digging into the innards of CKEditor, I found I can simply do this in my onReady event handler:
onReady(event: any){
event.editor.focus();
}
This worked the first time I tried it. Now since I've reloaded the app it no longer works...wth?
This might work for some:
onReady(event: any){
event.editor.focus();
}
but it takes the editor about 500-1000s milliseconds to load. So wrap the focus call in a timer:
onReady(event: any){
setTimeout(() => event.editor.focus(), 1000);
}
However, in my situation the user can show or hide the editor by clicking a button. Since the onReady event then only fires when the DOM is actually ready (since it is part of the config options), it only fires once.
Therefore a more creative solution is needed.
First, I created a private variable in my component to store a ref to the editor:
private ckEditor: any;
then, when the onReady fires the first time, I store the reference to the ckEditor instance:
onReady(event: any){
this.ckEditor = event.editor;
}
Now, when my user clicks the button that shows the editor:
onClickEditButton(){
this.isEditMode = true;
this.isShowEditButton = false;
setTimeout(() => this.ckEditor.focus(), 250);
}
I can set the focus every time it is shown! Yay! :)
Note that the timer can be much quicker here because by the time the user can click my Edit button (which fires this method), the DOM is fully loaded (as is the ckeditor plugin).
The above answer did not work for me. So I found this thread. Short answer, use $event.editing.view.focus().
Long answer, in my case, I needed to delay creating the editor element until the user clicks on the wrapping element (to improve performance). After the user clicked, the editor is then created (the user does not know that, he just clicks on an area that "looks" as if there is an editor).
So what you have to do in html is
<ckeditor
[editor]="editor"
(ready)="onEditorReady($event)"
data=""
name="text">
</ckeditor>
And the code
onEditorReady($event: any) {
$event.editing.view.focus();
}
No need for timeout. Also note that we are not working with the editor instance but with the $event instance.
The $event.editing.view objects prototype is where the focus() function is. Its also an object you can look for anything advanced that you would need to do programatically. For example, you can do $event.editing.view.destroy(). This will destroy the ckeditor but will still create a regular old textarea element. Not sure yet where this can be useful but here it is.
So I used this totally awesome tool called Visual Event, which shows all the event handlers bound to an object - and I noticed that every time I clicked or played around with my object and checked the list of event handlers bound to it, there were and more every time. My problem is this one: console.trace or stack trace to pinpiont the source of a bug in javascript? After using Visual Event and someone else's suggestion, I'm thinking my problem is that I'm probably binding the same handlers to the same events over and over again. Is there a way to unbind things regularly?
My application has a bunch of plugins connect to dynamically created divs. These divs can be resized and moved around the place. The application is a kind of editor, so users arrange these divs (which contain either images or text) in any design they like. If the user clicks on a div, it becomes "activated", while all other divs on the page get "deactivated". I have a bunch of related plugins, like activateTextBox, initTextBox, deactivateTextBox, readyTextBox, and so on. Whenever a div is first created, the init plugin is called once, just the first time after creation, like so:
$(mydiv).initTextBox();
But readyTextBox and activateTextBox and deactivateTextBox are called often, depending on other user events.
In init, I first use bind things like resizable() and draggable(), then I make the box "ready" for use
$.fn.extend({
initTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.mouseenter(function(){
if(!$this.hasClass('activated'))
$this.readyTextBox();
}
$this.mouseleave(function(){
if($this.hasClass('ready')){
$this.deactivateTextBox();
$this.click(function(e){
e.preventDefault();
});
}
});
});
});
Here's a simplified summary version of the readyTextBox plugin:
(function($){
$.fn.extend({
readyTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.resizable({ handles: 'all', alsoResize: img_id});
$this.draggable('enable');
$this.on( "dragstop", function( event, ui )
{/* some function */ });
$this.on("resizestop", function( event, ui ){ /* another function */ });
// and so on
});
Then there's activateTextBox():
$.fn.extend({
activateTextBox: function(){
return this.each(function() {
// lots of code that's irrelevant to this question
$this.resizable('option','disabled',true); //switch of resize & drag
$this.draggable('option', 'disabled', true);
});
Then deactivate, where I turn on draggable and resizable again, using the code:
$this.draggable('enable'); $this.resizable('option','disabled',false);
These divs, or "textboxes" are contained within a bigger div called content, and this is the click code I have in content:
$content.click(function(e){
//some irrelevant code
if( /* condition to decide if a textbox is clicked */)
{ $(".textbox").each(function(){ //deactivate all except this
if($(this).attr('id') != $eparent.attr('id'))
$(this).deactivateTextBox();
});
// now activate this particular textbox
$eparent.activateTextBox();
}
});
This is pretty much the relevant code related to text boxes. Why is it that whenever I drag something around and then check Visual Event, there are more clicks and dragstops and mouseovers than before? Also, the more user interacts with the page, the longer the events take to complete. For example, I mouseout from a div, but the move cursor takes a loooong time to get back to default. I quit dragging, but everything gets stuck for a while before getting ready to take more user clicks, etc. So I'm guessing the problem has to be that I'm binding too many things to the same events need to be unbinding at some point? It gets so bad that draggable eventually stops working at some point. The textboxes just get stuck - they're still able to be resized, but dragging stops working.
Am I binding events over and over
Yes. Have a look at your code:
$this.mouseenter(function(){
…
$this.mouseleave(function(){
…
$this.click(function(e){
…
});
});
});
That means every time you mouseover the element, you add another leave handler. And when you leave the element, every of those handlers adds another click event.
I'm not sure what you want to do, but there are several options:
bind the event handlers only once, and keep track of the current state with boolean variables etc.
before binding, remove all other event handlers that are already bound. jQuery's event namespacing can help you to remove only those which your own plugin added.
use the one() method that automatically unbinds a listener after firing it.
Heres my link:
http://tinyurl.com/6j727e
If you click on the link in test.php, it opens in a modal box which is using the jquery 'facebox' script.
I'm trying to act upon a click event in this box, and if you view source of test.php you'll see where I'm trying to loacte the link within the modal box.
$('#facebox .hero-link').click(alert('click!'));
However, it doesn't detect a click and oddly enough the click event runs when the page loads.
The close button DOES however have a click event built in that closes the box, and I suspect my home-grown click event is being prevented somehow, but I can't figure it out.
Can anyone help? Typically its the very last part of a project and its holding me up, as is always the way ;)
First, the reason you're getting the alert on document load is because the #click method takes a function as an argument. Instead, you passed it the return value of alert, which immediately shows the alert dialog and returns null.
The reason the event binding isn't working is because at the time of document load, #facebox .hero-link does not yet exist. I think you have two options that will help you fix this.
Option 1) Bind the click event only after the facebox is revealed. Something like:
$(document).bind('reveal.facebox', function() {
$('#facebox .hero-link').click(function() { alert('click!'); });
});
Option 2) Look into using the jQuery Live Query Plugin
Live Query utilizes the power of jQuery selectors by binding events or firing callbacks for matched elements auto-magically, even after the page has been loaded and the DOM updated.
jQuery Live Query will automatically bind the click event when it recognizes that Facebox modified the DOM. You should then only need to write this:
$('#facebox .hero-link').click(function() { alert('click!'); });
Alternatively use event delegation
This basically hooks events to containers rather than every element and queries the event.target in the container event.
It has multiple benefits in that you reduce the code noise (no need to rebind) it also is easier on browser memory (less events bound in the dom)
Quick example here
jQuery plugin for easy event delegation
P.S event delegation is pencilled to be in the next release (1.3) coming very soon.