I'm using Angular Highcharts, although I don't think it makes a lot of difference in this case. I'm using a responsive design, and the chart appears before it "figures out" the size of the parent element. Not a problem, except triggering reflow doesn't appear to work in the callback of redraw or load.
Here is the basic code:
events: {
redraw: function(event) {
$rootScope.$broadcast('highchartsng.reflow');
}
}
If I use "load" instead of redraw, it works even worse as it triggers first and doesn't recognize the highcharts object at all.
That's what MutationObserver is for. Available in IE11, https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver. You need to inject $element service into controller which semi-breaks DOM/controller separation, but I feel that this is a fundamental exception (ie. flaw) in angularjs. Since hide/show is async, we need on-show callback, that angularjs & angular-bootstrap-tab do not provide. It also requires that u know which specific DOM element u want to observe. I used following code for angularjs controller to trigger Highcharts chart reflow on-show.
const myObserver = new MutationObserver(function (mutations) {
const isVisible = $element.is(':visible') // Requires jquery
if (!_.isEqual(isVisible, $element._prevIsVisible)) { // Lodash
if (isVisible) {
$scope.$broadcast('onReflowChart')
}
$element._prevIsVisible = isVisible
}
})
myObserver.observe($element[0], {
attributes: true,
attributeFilter: ['class']
})
Related
I'm using fullcalendar 1.6.3 along with Drupal 7 (thus the need, for now, to be back on 1.6.3). I have some code that I'd like to run every time the view of my calendar changes (via ajax requests) -- forward or backward in time, or between month/week/day view.
Based on some tests, I could do this by hacking the source for renderEvents:
function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack
if (elementVisible()) {
currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
currentView.trigger('eventAfterAllRender');
// my_code_here();
}
}
but that would of course be Wrong. Unfortunately, I can't figure out any other way to do it, probably because of some obvious gap in my Javascript knowledge. I tried setting up an event handler on eventAfterAllRender:
var eventAfterAllRenderEvent = new Event('eventAfterAllRender');
document.addEventListener('eventAfterAllRender', function (e) {my_code_here() }, false);
document.dispatchEvent(eventAfterAllRenderEvent);
but that (for me) only runs on page load, not after the ajax events.
This is probably more of a Javascript question than a fullcalendar question, but is there any advice out there? I'm really trying to not hack core; thanks!
According to the documentation eventAfterAllRender is a callback, so you can do this:
$('#your-calendar-element').fullCalendar({
eventAfterAllRender: function (view) {
// add your code here
}
});
Im developing some web-automatization tool (C# but this topic is not abou it..).
Tool unsing WebBrowser as a browser and connect to some web-site.
Web site has dynamic content, it updates once per second. Web site using setTimeout to update its content. Then data resopnsed web site update content inside DOM element (table). Its delete 3 old rows and insert 3 new rows.
My target is to make a trigger for this event.
I checked and the website doesn't use document.write. As I understand there are some ways the site can modify content. First is innerHTML property, but table has 4 rows , site dont update/delete first row. So another way is appendChild and removeChild its more real.
So my question is how i can trigger this action?
As i know it possible to define my own funcs for DOM element. So how i can redefine appendChild and removeChild methods?
Possible something like this?:
var realRC = contentTable.removeChild;
contentTable.removeChild = function(el){
alert("Trigged!");
realRC(el);
}
I assume you mean trigger when you say "trig".
Triggering it
In browsers timeouts and intervals all have IDs, you can clear them but you can't trigger it directly. The best bet would be looking for setTimeout/setInterval statements and replicating them.
Listening to the event
Anyway, there is a dirty way and a clean way. Let's assume you have access to your table in:
var target = document.querySelector('#some-id');
The clean way would be using DOM Mutation Observers
// Example from MDN
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type); // here you'll get the changes
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
The dirty way would be dirty checking
It's better supported and more stable since observers are really new and there are still issues.
Every 16 miliseconds check if the .innerHTML value of the table changed.
var oldHTML = "";
setTimeout(function(){
if(target.innerHTML !== oldHTML){
changed(); // changed is a function that you define and notify
}
},16);
If there are HTML changes like position/etc you can use .textContent instead.
Honestly, the dirty way is simpler and more stable - but uglier. Still I'd go for it.
Benjamin Gruenbaum solve it)
Im post this here for more readable!
var dataTable = document.getElementById("event_1538016");
dataTable.appendChild = function(node)
{
console.log("trigged!");
document.appendChild.apply(dataTable,[node]); // args is array!!
return node;
}
works perfectly on mozilla! Im hope it will works on IE9+
Technical context:
dojo 1.8.1
dgrid 0.3.4-pre
IE 10.0.9200
Short version:
I have some dojo/on events I'm listening too (from _WidgetBase), but they'll sometimes get called and sometimes don't. dgrid, pagination and IE are in the mix. What could be the problem?
Details:
I have this weird situation with a dgrid grid, and as much as I've tried, I have not been able to isolate the actual cause of the issue. I'll be as thorough as I can, but feel free to ask for more information.
I have a dgrid component that's using pagination and a combo box that has some values on which I will filter by. This is pretty much how it is set up (simplified, actual code references other modules and has stuff more modularized):
// GridContainerWidget
var CustomGrid = declare([Grid, CompoundColumns, Selection, Keyboard, Pagination], {
selectionMode: "single",
rowsPerPage: 20
});
var grid = new CustomGrid({
deselectOnRefresh: false
}, domContainer);
The grid is later on bound to a JsonStore.
My filter is set up in the following way:
// SearchBarWidget
// inside a custom widget, inheriting from _WidgetBase
var self = this;
var statusCombo = new ComboBox({
store: new Memory(/* data and labels */),
onChange: function (selection) {
self.emit("status_changed", self.getSomeData());
}
}, comboDomContainer);
And back to the widget that encapsulates both the filter and the grid:
// GenericListWidget, contains both the SearchBarWidget and the GridContainerWidget
var self = this;
this._searchToolbar.on("status_changed", function (data) {
// ... some "calibrations" ...
self._grid.set("query", { newCriteria: "something" });
});
This is where it gets weird:
Sometimes in IE emit("status_changed") will be called but the callback on("status_changed") won't be called. Some other times, emit will be called and the callback will be called too (expected).
This has to do with the pagination or the grid refresh somehow, but I've not been able to isolate the exact same steps that reproduce the issue
In digging deeper into this, I've seen IE reaches the point where it executes the following: (dojo/on, lines 314 to 322)
var nativeEvent = target.ownerDocument.createEvent("HTMLEvents");
nativeEvent.initEvent(type /* "status_changed" */, !!event.bubbles /* true */, !!event.cancelable /* true */);
// ... copies properties ...
return target.dispatchEvent(nativeEvent) && nativeEvent; // returns true
I verified that the remove() call for the on hook is never being called
I verified that the DOM element on which events are being triggered and listened onto are actually the same, every time
My question is: how can I find the underlying issue and make sure that my on() callback gets called every time?
You are running into https://github.com/SitePen/dgrid/issues/379 which was fixed in dgrid 0.3.7. To resolve the issue as it pertains to the paging controls in the Pagination extension, you will either need to update your version of dgrid or apply the changeset yourself.
This is due to a rather ridiculous and annoying IE bug present in IE9 and IE10 (fixed in IE11):
https://connect.microsoft.com/IE/feedback/details/799780/ie10-addeventlistener-function-is-fired-by-a-different-event
https://connect.microsoft.com/IE/feedback/details/802397/ie9-ie10-events-can-be-sent-to-the-wrong-listeners
I'm using Backbone.js, and in one of my main views I've encountered a very strange bug that I can't for the life of me figure out how to solve.
The view looks a look like the new Twitter layout. It receives an array of objects, each of which describes a collection and views elements that act on that collection. Each collection is represented by one tab in the view. The render() method on my view takes this array of collection objects, clears out the tabContainer DOM element if it isn't already empty, renders the tabs and then binds events to each of those tabs.
Now in my code I have the method to render the tabs and the method to bind the click handlers to those tabs sequentially. This works fine the first time I execute render(), but on subsequent calls of render(), the click handlers are not bound. Here's the relevant code snippet:
initialize: function() {
// Context on render(), _addAllTabs and _bindTabEvents is set correctly to 'this'
_.bindAll(this, 'render', 'openModel', 'closeModel', 'isOpen', 'addAllModels', '_switchTab',
'addOneModel', '_addTab', '_removeTab', '_addAllTabs', '_loadCollection',
'_renderControls', '_setCurrentCollection', '_loadModels', '_bindTabEvents');
this.template = JST['ui/viewer'];
$(this.el).html(this.template({}));
// The tabContainer is cached and always available
this.tabContainer = this.$("ul.tabs");
this.collectionContainer = this.$("#collection_container");
this.controlsContainer = this.$("#controls");
this.showMoreButton = this.$("#show_more_button");
},
render: function(collections, dashboard) {
// If _bindTabEvents has been called before, then this.tab exists. I
// intentionally destroy this.tabs and all previously bound click handlers.
if (this.tabs) this.tabContainer.html("");
if (collections) this.collections = collections;
if (dashboard) this.$("#dashboard").html(dashboard.render().el);
// _addAllTabs redraws each of the tabs in my view from scratch using _addTab
this._addAllTabs();
// All tabs *are* present in the DOM before my _bindTabEvents function is called
// However these events are only bound on the first render and not subsequent renders
this._bindTabEvents();
var first_tab = this.collections[0].id;
this.openTab(first_tab);
return this;
},
openTab: function (collectionId, e) {
// If I move _bindTabEvents to here, (per my more thorough explanation below)
// my bug is somehow magically fixed. This makes no friggin sense.
if (this.isTabOpen(collectionId)) return false;
this._switchTab(collectionId, e);
},
_addAllTabs: function() {
_.each(this.collections, this._addTab );
},
_bindTabEvents: function() {
this.tabs = _.reduce(_.pluck(this.collections, "id"), _.bind(function (tabEvents, collectionId) {
var tabId = this.$("#" + collectionId + "_tab");
tabEvents[collectionId] = tabId.click(_.bind(this._switchTab, this, collectionId));
return tabEvents
}, this), {});
},
_addTab: function(content) {
this.tabContainer.append(
$('<li/>')
.attr("id", content.id + "_tab")
.addClass("tab")
.append($('<span/>')
.addClass('label')
.text(content.name)));
//this._loadCollection(content.id);
this.bind("tab:" + content.id, this._loadCollection);
pg.account.bind("change:location", this._loadCollection); // TODO: Should this be here?
},
etc..
As I said, the render() method here does work, but only the first time around. The strange part is that if I move the line this._bindTabEvents(); and make it the first line of the openTab() method like in the following snippet, then the whole thing works perfectly:
openTab: function (collectionId, e) {
this._bindTabEvents();
if (this.isTabOpen(collectionId)) return false;
this._switchTab(collectionId, e);
},
Of course, that line of code has no business being in that method, but it does make the whole thing work fine, which leads me to ask why it works there, but doesn't work sequentially like so:
this._addAllTabs();
this._bindTabEvents();
This makes no sense to me since, it also doesn't work if I put it after this line:
var first_tab = this.collections[0].id;
even though that is essentially the same as what does work insofar as execution order is concerned.
Does anyone have any idea what I'm doing wrong and what I should be doing to make this correct (in terms of both behavior and coding style)?
In your view's render function, return this.delegateEvents(); I think you are losing your event bindings across your renderings and you need to re-establish them.
See this link for the backbone.js documentation for that function:
backbone.js - delegateEvents
When you switch tabs you are not simply showing/hiding content you are destroying and rebuild dom element so you are also destroying event liseners attached to them. that is why the events only work once and why adding _bindTabEvents into render works, because you are re-attaching the events each time.
when this line executes : this.tabContainer.html(""); poof... no more tabs and no more tab events.
I have an ajax callback which injects html markup into a footer div.
What I can't figure out is how to create a way to monitor the div for when it's contents change. Placing the layout logic I'm trying to create in the callback isn't an option as each method (callback and my layout div handler) shouldn't know about the other.
Ideally I'd like to see some kind of event handler akin to $('#myDiv').ContentsChanged(function() {...}) or $('#myDiv').TriggerWhenContentExists( function() {...})
I found a plugin called watch and an improved version of that plugin but could never get either to trigger. I tried "watching" everything I could think of (i.e. height property of the div being changed via the ajax injection) but couldn't get them to do anything at all.
Any thoughts/help?
The most effective way I've found is to bind to the DOMSubtreeModified event. It works well with both jQuery's $.html() and via standard JavaScript's innerHTML property.
$('#content').bind('DOMSubtreeModified', function(e) {
if (e.target.innerHTML.length > 0) {
// Content change handler
}
});
http://jsfiddle.net/hnCxK/
When called from jQuery's $.html(), I found the event fires twice: once to clear existing contents and once to set it. A quick .length-check will work in simple implementations.
It's also important to note that the event will always fire when set to an HTML string (ie '<p>Hello, world</p>'). And that the event will only fire when changed for plain-text strings.
You can listen for changes to DOM elements (your div for example) by binding onto DOMCharacterDataModified tested in chrome but doesn't work in IE see a demo here
Clicking the button causes a change in the div which is being watched, which in turn fills out another div to show you its working...
Having a bit more of a look Shiki's answer to jquery listen to changes within a div and act accordingly looks like it should do what you want:
$('#idOfDiv').bind('contentchanged', function() {
// do something after the div content has changed
alert('woo');
});
In your function that updates the div:
$('#idOfDiv').trigger('contentchanged');
See this as a working demo here
There is a neat javascript library, mutation-summary by google, that lets you observe dom changes concisely. The great thing about it, is that if you want, you can be informed only of the actions that actually made a difference in the DOM, to understand what I mean you should watch the very informative video on the project's homepage.
link:
http://code.google.com/p/mutation-summary/
jquery wrapper:
https://github.com/joelpurra/jquery-mutation-summary
You might want to look into the DOMNodeInserted event for Firefox/Opera/Safari and the onpropertychange event for IE. It probably wouldn't be too hard to utilize these events but it might be a little hack-ish. Here is some javascript event documentation: http://help.dottoro.com/larrqqck.php
Now we can use a MutationObserver ; Well, apparently we must.
Use of Mutation Events is deprecated. Use MutationObserver instead.
jquery.min.js:2:41540
From https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver :
// Select the node that will be observed for mutations
const targetNode = document.getElementById('some-id');
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();