I'd like to modify a div inside my Vue component's template using the Rangy library. I have code like this in one of my methods:
let tooltip = document.createElement('button');
tooltip.setAttribute('type', 'button');
tooltip.setAttribute('v-tooltip.top-center', '"msg"');
tooltipSpan.innerHTML = 'x';
// Insert span with tooltip after of mistake
range.collapse(false);
range.insertNode(tooltipSpan);
As you can see in the third line, I also want to use a v-tooltip component. If I could hardcode this inside the template, it'd simply be <button v-tooltip.top-center="msg">x</button>. But in my app, this component could be programmatically placed anywhere inside a div, so I need to insert it with JavaScript. There might also be more than one instance of that component.
Unfortunately, my approach does not work at all. It doesn't matter whether I use the approach mentioned above using the v-tooltip directive or if I simply insert a component (e.g., let tooltip = document.createElement('my-custom-tooltip');): The new DOM element is correctly inserted, but Vue does not recognize/interpret it as a component. In the above example, all I get is a simple, unstyled button element.
I tried this.$forceUpdate(), this.$nextTick() and a bunch of older methods (that have probably vanished from Vue 2), but nothing worked.
Is there a way to make Vue re-render after manual changes to he DOM, correctly interpreting components that have been inserted?
Vue "owns" the DOM tree belonging to any component; if you mess around in the DOM like you are, any Vue-specific things that you create or modify (like directives) won't magically work.
The DOM that is generated by Vue is a function of the component's data; any time the data changes Vue will re-render the DOM to reflect that change. Vue doesn't know how to deal with manual changes you make to the DOM.
I don't know specifically what you are trying to do and why Rangy is needed, but the correct way to do something like this would be to modify your data not the DOM, and then your template (or render function) takes care of rendering the new data.
Is there a way to make Vue re-render after manual changes to he DOM, correctly interpreting components that have been inserted?
No.
Related
How can I insert a DOM node using vanilla javascript into a hierarchy of DOM nodes created by react such that React will not remove it when the state changes.
Reason:
I am developing a browser extension, and would like to tightly integrate with an existing site. The existing site uses React under the hood, and I would like to insert a node into the DOM.
Issue:
Inserting the node works, but as soon as some state in the react app changes, it removes the custom node from the DOM.
How can I insert and element which will not be removed?
I don't think you can (but you might be able to work around it).
React's job is, in large part, to reconcile the state of the DOM with the state of the React element tree the code using React gives it. After the code has "rendered" React elements, React "commits" that structure to the DOM, doing a diff between the structure the React element tree describes and the structure that's there: removing things that shouldn't be there, adding things that are missing, and updating things that should be there but need their state updated. When it does that, it will see the element you added, see that it's not supposed to be there, and remove it.
The first workaround that comes to mind for your use case is to add the element and use a MutationObserver to watch for it being removed, adding it back if (when) it is.
Suppose one comes from jQuery world. If one has a component myPlugin (jQuery plug-in, for example a component showing tooltips when one hovers on specific DOM elements) it would be initiated like this:
$('.tooltipClass').myPlugin( { // options object here } )
This code will select all elements with a class tooltipClass and will apply the myPlugin on them.
In Vue.js it is different and the documentation does not really make it clear. We have
let x = new Vue({
el: '#app',
...
})
and then we read (in the documentation):
Provide the Vue instance an existing DOM element to mount on. It can be a CSS selector string or an actual HTMLElement.
But a CSS selector could return multiple elements (it won't work for multiple elements in Vue.js - as I've already tried).
So is there a way to mimic the jQuery initialization on multiple elements in Vue.js?
(I know one could achieve it manually outside the Vue.js)
The Vue.js documentation is also quite unclear on the topic. The documentation for version 3.x does not include the el option at all and it is quite hard to decide from there how to proceed in cases as in the example above.
For example one creates a popup Vue.js component and wants to show it when the user clicks on different buttons on the page. The buttons may have role='popup' attribute in the HTML and show the parameterized popup (depending on some other data-xxx attributes for example).
This is pretty standard with all javascript components nowadays. How would one achieve it with Vue.js?
If you want to attach vue to multiple elements you need to create multiple Vue instances:
document.querySelectorAll('.sampleClass').forEach(x => new Vue({el:x}));
But you need to keep in mind, that Vue works in different way that jQuery. After creating Vue instance, Vue will be responsible for creating everythink inside this element, so you shouldn't use Vue to add single behaviour to existing element, rather to create whole component with all it's behaviours with Vue.
In your example with popup you should add event listener with plain javascript and when event is fired, then create Vue instance with popup.
I'm currently trying to understand why this example is not working as expected. So what I'm trying to achieve is to initialize my ContentView with the server side rendered HTML present in the DOM. Therefore I intend to check if init__main-content exists to then initialize the component with the innerHTML. Afterwards I simply tried to delete the initial node but this is not working and I end up with the content shown twice.
There must be some vue "magic" I'm not aware of, can please someone explain this behavior?
I guess there is problem in your code as you are using created hook insted you can use mounted hook.
so after mounting vue does finish all its magic to dom and now your changes can be persisted in to the dom.
if you use created then vue may be using that inner html as template and rewrite it it to dom thinking that previous html is not matching with current html when instance is created. as you already removing that element. so it will try to make it correct and add previous html again to maintain virtual-dom and real-dom changes.
if you use mounted hook your application is working fine try it.
I have been using React from couple of months and React doesn't simply re-rendering a component completely instead it finds the difference and makes those changes. Does Angular 2 does something like this?
And also whenever a change in state is detected does Angular 2 re-render all the components from the root node or does it only re-render those specific components and their sub-tree whose change is detected?
React doesn't simply re-rendering a component completely instead it finds the difference and makes those changes. Does Angular 2 does something like this?
Conceptually yes, it does not re-render entire components.
Angular builds a change detector object for each component/directive. Template bindings (which includes input property bindings) are tracked inside these change detector objects. When change detection runs, by default, each binding is dirty checked for changes. If a change is found, the changed value is propagated to the child component (if an input property changed) or to the DOM. That's it. The entire template/view is not re-rendered. Only the changed values are updated in the DOM. When Angular change detection finishes, the browser notices the DOM changes and updates what we see on the screen.
whenever a change in state is detected does Angular 2 re-render all the components from the root node or does it only re-render those specific components and their sub-tree whose change is detected?
Angular doesn't detect changes to some model/data objects. Rather, it only detects changes to template bindings.
By default, each time change detection runs, it starts from the root component and checks all components for changes, in depth-first order, using those change detector objects. As described above, only template bindings with changes are updated. So, I wouldn't say that Angular ever re-renders a component... it only modifies those parts of the DOM where a template binding changed.
You can configure a component to use the OnPush change detection strategy to limit when that component and its descendants are checked for changes. You can also completely detach() a component from the change detector tree, which means that component and its descendants will not be change detected until you reattach().
Angular is not using virtual DOM as React do. No need for that in context of Angular.
If you have <input> and need to set its value in runtime to something else you don't need to change all DOM around it. You just call setValue() on that element and that's it.
The same applies to any other DOM element. For example if you have this:
<div>{{someVar}}</div>
and Angular detects that someVar was changed it will change content of only that particular <div>.
Angular only renders where it detects changes.
AFAIK there is some room for improvments in *ngFor where it sometimes re-renders too many items when some are added/removed in the middle or the beginning but that is a known issue and will be fixed eventually.
From my comment below
In fact Angular doesn't need re-rendering optimization because it only does anything when bound values change and then it only changes the DOM where it is bound to the changed value. Angular doesn't have a virtual DOM that it needs to mirror to the actual DOM.
Angular2 is using zone.js for onChange rendering. Usually when a change is detected, it will trigger changeDetection that component and all the children, but you also can have control to change that, to force render some things or not render when you don't like angular2 behavior.
Here is a very good talk about how Angular2 change detection works : https://www.youtube.com/watch?v=CUxD91DWkGM
LE: Just to clarify, it will not re-render the component and all the children, it will detect and trigger changes for all of those, but will render only what is necessary.
I have an Ember component that uses jQuery to add a canvas chart. When I change routes, I get a new model, but Ember's automatic rerendering does not work in this case. In fact, I don't know how to make the component code which adds the chart re-run at all. How can I do this?
Would it work better if it was a view?
Without looking at code, I'm going to guess based on your statement of the model changing.
Component Currently
uiSetup: function(){
// do magic here...
}.on('didInsertElement')
Component with observes
Assuming the model in the component is named model, this would fire every time the model changed as well as when the element was initially inserted into the page. You could also break it into two separate functions if you need it to act differently on model change vs element inserted first time.
uiSetup: function(){
// do magic here...
}.on('didInsertElement').observes('model')