Mutation Observer does not detect nodes added through innerHTML, appendChild - innerhtml

When we try to add nested nodes in DOM using appendChild or innerHTML, the nested nodes do not come in the addedNodes of a mutation.
Initial HTML setUp:
<html>
<body>
<div id="container">
</div>
</body>
</html>
Here is my Mutation Observer code:
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
switch(mutation.type) {
case 'childList':
console.log(mutation.addedNodes);
break;
default:
}
}
});
observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
If I now do appendChild with a nested img node in a div,
var img = document.createElement('img');
img.setAttribute("src", "http://img.zohostatic.com/discussions/v1/images/defaultPhoto.png");
var div = document.createElement('div');
div.appendChild(img);
var container = document.getElementById("container");
container.appendChild(div);
The Mutation Observer only logs the div which is appended to the container, but does not log the img as an addedNode in mutation.
Here is a working JSFiddle: https://jsfiddle.net/aedhefhn/
Is there a workaround for this?

That is the expected behavior. The div was all that was appended, it just happened to have some children. You can certainly walk through its child nodes yourself from .addedNodes though. .addedNodes is just an array of nodes, so you can go through each item recursively to get all the children.

Related

Vuejs same function on multiple divs need to run seperately

Alright, I have these two divs with a mouseover and they have the same function. Now the problem is that if I mouse over one of them then BOTH shines. How to solve this? So shines one by one when I hover them.
DIVS:
<div class="latestItemBody" #mouseover="shineItemIcon"
#mouseout="shineOff" :style="{background: activeCardBg}">
<div class="latestItemBody" #mouseover="shineItemIcon"
#mouseout="shineOff" :style="{background: activeCardBg}">
Functions:
methods: {
shineItemIcon() {
this.activeCardBg = '#7a00ff';
this.bounce = 'animated bounceIn';
},
shineOff() {
this.activeCardBg = '';
this.bounce = '';
}
The reason why they both "shine" is because you have activeCardBg bound to both of them, which changes the background.
You could add the shine effect with pure CSS like this instead.
// CSS
.latestItemBody:hover {
background-color: #7a00ff
}
If you want to do this with JS, it could be done like this.
// Template
<div
class="latestItemBody"
#mouseover="shineItemIcon"
#mouseout="shineOff">
</div>
<div
class="latestItemBody"
#mouseover="shineItemIcon"
#mouseout="shineOff">
</div>
// Methods
shineItemIcon(e) {
e.target.style.backgroundColor = '#7a00ff';
this.bounce = 'animated bounceIn';
},
shineOff(e) {
e.target.style.backgroundColor = '';
this.bounce = '';
}
pass the div id as parameter to the shineitemicon and shineoff function. depending upon the condition set 'activeCardBg' value. give activecardBg1 to first div and activeCardBg2 to second div.

How to change contents of a virtual dom element in Mithril?

How do I access a virtual dom element to change its contents using Mithril? I am new to Mithril and still trying to figure things out. For example, I want to access the third div with id "three" and change it's contents to "Blue Jays" without touching any of the other div's.
Thanks.
<div id='main'>
<div id='one'>Yankees</div><br>
<div id='two'>Red Sox</div><br>
<div id='three'>Orioles</div>
</div>
In mithril, like in react/vue/angular, you dont act on the actual DOM directly. Instead, you define the outcome that you want, so for example, to render the DOM tree that you posted you would do something like this:
var my_view = {
view: vnode => m('div#main', [
m('div#one', 'Yankees'),
m('div#two', 'Red Sox'),
m('div#three', 'Orioles')
])
}
m.mount(root, my_view)
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.4/mithril.js"></script>
<div id="root"></div>
the m(...) functions inside the array have a string as their second argument, that makes the output static, but we can change that to a variable:
var my_view = {
oninit: vnode => vnode.state.fave_team = 'Orioles',
view: vnode => m('div#main', [
m('div#one', 'Yankees'),
m('div#two', 'Red Sox'),
m('div#three', vnode.state.fave_team)
])
}
m.mount(root, my_view)
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.4/mithril.js"></script>
<div id="root">
</div>
In this case I used the state property of the vnode argument, but you can also use a third party state manager like flux or any other.
Now that we have it as a variable, it will show the current value on every call m.redraw(), most of the times we dont have to do this call ourselves, for example:
var my_view = {
oninit: vnode => {
vnode.state.fave_team = 'Orioles'
},
view: vnode => m('div#main', [
m('div#one', 'Yankees'),
m('div#two', 'Red Sox'),
m('div#three', vnode.state.fave_team),
m('button', { onclick: () => vnode.state.fave_team = 'Dodgers' }, 'Change')
])
}
m.mount(root, my_view)
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.4/mithril.js"></script>
<div id="root"></div>
And thats it, any dynamic content in your DOM elements you set it as a variable/property in an object.
One of the beautiful things about mithril is that it doesnt force you to do things one specific way, so if you really want to work on the actual DOM node, there are lifecycle events that you can attach to any virtual node ("vnode")
You can easily capture the HTMLElement (i.e., HTMLInputElement) with the Mithril Lifecycle event of oncreate(). This is an actual example from my code (in TypeScript) where I need to hook up a few event listneres after the canvas element was created and its underlying DOM is available to me at "raw" HTML level. Once you get a hold of dom, then I manipulate that element directly. Many people think that why not use oninit(), but oninit() is before the generation of dom, so you will not get the element back at that stage.
Now, if you just do that, you will likely be posting another question - "Why the browser views not updating?" And that's because you do have to manually do a m.redraw() in your event handlers. Otherwise Mithril would not know when the view diffs to be computed.
const canvas = m(`.row[tabIndex=${my.tabIndex}]`, {
oncreate: (element: VnodeDOM<any, any>) => {
const dom = element.dom;
dom.addEventListener("wheel", my.eventWheel, false);
dom.addEventListener("keydown", my.eventKeyDown, false);
}
},

Element is undefined because it is not rendered yet. How to wait for it?

In my Vue app I have an image element with a bound src. this code is in a stepper. In one step the user selects a picture to upload an in the next step the image gets shown (which works as intended). However I am trying to initiate a library on the element in the next step (the Taggd library) but the element seems to be undefined.
I tried the nextTick() to wait for the image to load but without luck.
the image element:
<div class="orientation-landscape">
<div class="justify-center q-px-lg">
<img v-if="photo.url" :src="photo.url" id="workingPhoto"
style="width: 80vw;" ref="workingPhoto"/>
</div>
</div>
The functions. I first use uploadFile to set the url of the image element and go to the next step where the image will be loaded. then I call initTaggd() on the nextTick however that fails because the element is undefined.
uploadFile(file) {
const self = this;
self.photo.file = file;
self.setPhotoUrl(file);
self.$refs.stepper2.next();
console.log('We should be at next step now. doing the init');
self.nextTick(self.initTaggd());
},
setPhotoUrl(file) {
const self = this;
self.photo.url = URL.createObjectURL(file);
console.log('ping1');
},
initTaggd() {
const self = this;
const image = document.getElementById('workingPhoto');
console.log('image:', image); //THIS RETURNS UNDEFINED
console.log('Trying ANOTHER element by refs:', self.$refs.stepper2); // This returns the element
console.log('trying the real element by reference', self.$refs.workingPhoto); // Returns undefined again
const taggd = new Taggd(image); //All this here fails because image is undefined.
console.log('ping2.5');
taggd.setTags([
self.createTag(),
self.createTag(),
self.createTag(),
]);
console.log('ping3');
},
I think I am looking for a way to wait for the image to fully load before calling initTaggd() but I am lost on how to achieve this.
You could listen for the load event:
<img
v-if="photo.url"
id="workingPhoto"
ref="workingPhoto"
:src="photo.url"
style="width: 80vw;"
#load="initTaggd"
/>

JQuery UI Tabs: nested Tabs, AJAX loading and Back button

I'm loading JQuery UI tabs using AJAX.
I have 3 levels of nested UI tabs:
vertical
horizontal
horizontal
Most of the stuff is only 2 levels deep but some are 3. The issue is the 3rd level. I followed this:
http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
But it does not cover nesting of tabs.
The layout of the page is like this:
<script type="text/javascript">
var tabs;
var tab_a_selector;
var tab_a_vertical_selector;
$(function() {
$("#menuTabs").tabs({
ajaxOptions: {
cache: false
}
}).addClass("ui-tabs-vertical ui-helper-clearfix");
$("#menuTabs li").removeClass('ui-corner-top').addClass('ui-corner-left');
$(".ui-tabs-vertical .ui-tabs-nav").removeClass("ui-tabs-nav").addClass("ui-tabs-nav-vert")
$("#menuItem0").tabs();
$("#menuItem1").tabs();
//...
/* -- enables Back button for nested tabs -- */
// The "tab widgets" to handle.
tabs = $('.tabs');
// This selector will be reused when selecting actual tab widget A elements.
tab_a_selector = 'ul.ui-tabs-nav a';
tab_a_vertical_selector = 'ul.ui-tabs-nav-vert a';
// Enable tabs on all tab widgets. The `event` property must be overridden so
// that the tabs aren't changed on click, and any custom event name can be
// specified. Note that if you define a callback for the 'select' event, it
// will be executed for the selected tab whenever the hash changes.
tabs.tabs({ event: 'change' });
// Define our own click handler for the tabs, overriding the default.
tabs.find(tab_a_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the new state
// This is done as below to remove any state from deeper levels of nested tabs.
state ['menuTabs'] = $.bbq.getState('menuTabs');
state[ id ] = idx;
$.bbq.pushState( state, 2 );
});
tabs.find(tab_a_vertical_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
// 2 -> replaces old state with new state. meaning indexes of nested tabs are removed
$.bbq.pushState( state, 2 );
});
// Bind an event to window.onhashchange that, when the history state changes,
// iterates over all tab widgets, changing the current tab as necessary.
$(window).bind( 'hashchange', function(e) {
// Iterate over all tab widgets.
tabs.each(function(){
// Get the index for this tab widget from the hash, based on the
// appropriate id property. In jQuery 1.4, you should use e.getState()
// instead of $.bbq.getState(). The second, 'true' argument coerces the
// string value to a number.
var idx = $.bbq.getState( this.id, true ) || 0;
// Select the appropriate tab for this tab widget by triggering the custom
// event specified in the .tabs() init above (you could keep track of what
// tab each widget is on using .data, and only select a tab if it has
// changed).
$(this).find( tab_a_selector).eq( idx ).triggerHandler( 'change' );
$(this).find( tab_a_vertical_selector ).eq( idx ).triggerHandler( 'change' );
});
})
// Since the event is only triggered when the hash changes, we need to trigger
// the event now, to handle the hash the page may have loaded with.
$(window).trigger( 'hashchange' );
/* -- END enables Back button for nested tabs -- */
});
</script>
<div id="menuTabs" class="tabs">
<ul>
<li>menuItem0</li>
<li>menuItem1</li>
<li>menuItem2</li>
</ul>
<div id="menuItem0" class="tabs">
<ul>
<li>Intro</li>
</ul>
</div>
<div id="menuItem1" class="tabs">
<ul>
<li>Introduction</li>
<li>Guide</li>
<li>abc</li>
</ul>
</div>
<!--...-->
</div>
The 3rd level of tabs is in the above html pages, as example in abc.html:
<script type="text/javascript">
var rNumberTabs = $("#rNumber").tabs();
rNumberTabs.tabs({ event: 'change' });
rNumberTabs.find(tab_a_selector).click(function(){
var state = {};
// Get the id of this tab widget.
id = $(this).closest( '.tabs' ).attr( 'id' );
// Get the index of this tab.
idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
$.bbq.pushState( state );
});
tabs = tabs.add(grNumberTabs);
// If this is triggered it leads to an infinte loop,
// if not, I can't even browse to any other tab than the first
// one on he third level, eg. it automatically jumps back
// to first one.
$(window).trigger( 'hashchange' );
</script>
<div id="rNumber" class="tabs">
<ul>
<li>Layout</li>
<li>Prefix</li>
</ul>
<div id="layout">
<!-- Content here -->
</div>
<div id="prefix">
<!-- Content here -->
</div>
</div>
Any ideas how I can solve?
So here my current solution. The issue of endless looping seems to be caused by the fact that when using AJAX loading of tabs, the tab is loaded again for every click on it. Since the tabs that contain a 3rd level of tabs also contain JavaScript (see question) re-loading such a tab leads to issues.
The solution is to cache the tabs:
$("#menuTabs").tabs({
cache: true,
ajaxOptions: {
cache: false
}
})
Note: You must set Ajax cache to false:
http://docs.jquery.com/UI/Tabs#option-cache
Same for the 3rd level of tabs. And here also remove the call to hashchange event.
var rNumberTabs = $("#rNumber").tabs({
cache: true,
ajaxOptions: {
cache: false
}
});
//snipped..
//$(window).trigger( 'hashchange' ); remove this line

Backbone: Pattern for rendering and re-rendering subscriber views in a correct order

So basically I've been toying with this pattern of pubsub with subviews re-rendering when called by parent 'controller' view (sorry if that's confusing). If the subviews are based on a fetch of a collection or model (not shown below), sometimes they aren't rendered in the correct order I need them too, ie if SubView1 is a small nav for Subview2, I don't want it below SubView2.
I figure there has to be a pattern for such a common problem. Lemme know if this doesn't make sense and I will clarify. But the basic situation I'm dealing with is below.
markup:
<div id="main-container">
<div class="inner-container"></div>
</div>
js:
var ToggleNavView = Backbone.View.extend({
//let's say this template has two links
template: Handbars.compile(linkstmpl),
el: $('#main-container'),
events: {
"click a": "toggleViews"
}
initialize: function(){
_.bindAll(this, 'render', 'toggleViews');
// whoa, nice looking event aggregator http://bit.ly/p3nTe6
this.vent = _.extend({}, Backbone.Events);
this.render();
},
render: function(){
$(this.el).append(this.tmpl);
// suppose subviews below are declared as modules above with, say, requirejs
var sub1 = new SubView1({ vent: this.vent }),
sub2 = new SubView2({ vent: this.vent });
},
toggleViews: function(e){
e.preventDefault();
// get name of section or view you're toggling to
var section = $(e.currentTarget).data('section');
// publish events to subscribers
this.vent.trigger('toggleInboxViews', this, section);
},
});
var SubView1 = Backbone.View.Extend({
template: Handlebars.compile(tmpl1),
initialize: function(ops){
_.bindAll(this, 'render', 'removeOrRerender');
this.vent = ops.vent || null;
this.render()
},
render: function(){
$(this.el).append(this.template()).appendTo($(".inner-container"))
},
removeOrRerender: function(obj, section){
if( section == 'my-section'){
this.render();
} else if( section == 'other-section' ) {
$(this.el).fadeOut();
}
},
})
// another subview with same functionality etc...
// init view
new ToggleNavView();
If you need your sub-views to show up in specific places then the parent view should define that structure and the sub-views should be told where to render themselves. Then it won't matter what order things are draw in as the overall structure doesn't change.
For example, if you want one sub-view to appear at the top and the other below it, the main view should look like this:
<div id="main-view">
<div id="sub1"></div>
<div id="sub2"></div>
</div>
then the main view would render the sub-views with something like this:
var sub1 = new SubView1({ el: this.$el.find('#sub1'), vent: this.vent }),
var sub2 = new SubView2({ el: this.$el.find('#sub2'), vent: this.vent });
By specifying the el for the sub-views, their location on the page is no longer their problem and they won't shift positions if they're rendered in a different order. A happy side effect of this structure is that the sub-views are only concerned with themselves and are nicely self-contained; the parent view just needs to put the pieces in the right place by structuring its template properly and everything just works.
Here's a simple demo that might clarify the structure: http://jsfiddle.net/ambiguous/9u3S5/