Calculate element height in directive - javascript

Why is nextTick required in my example below in order to calculate the element height? As far as I know, nextTick is used to wait for DOM updates after reactive data has been modified, which is not the case below:
Vue.directive('test', {
bind: function(el) {
console.log($(el).css('height'))
Vue.nextTick(() => {
console.log($(el).css('height'))
})
}
});
var demo = new Vue({
el: '#demo'
})
<div id="demo">
<div v-test>
lorem
lorem
</div>
</div>
http://jsfiddle.net/LPfpS/463/

You are using the wrong hook.
Hook Functions A directive definition object can provide several hook
functions (all optional):
bind: called only once, when the directive is first bound to the
element. This is where you can do one-time setup work.
inserted: called when the bound element has been inserted into its
parent node (this only guarantees parent node presence, not
necessarily in-document).
Vue.directive('test', {
inserted: function(el) {
console.log($(el).css('height'))
}
});
var demo = new Vue({
el: '#demo'
})
See Fiddle

Related

vue.js - dynamic html tag (using variable)

I was wondering if there was any way to dynamically set the tags of an html element. E.g.
var element = "ol";
<{element}> some content </{element}>
Easier method is to just use component element, like this:
<component :is="elType">...</component>
Just set elType in data to whatever type of element you want it to be (ie div, h1 etc)
Demo:
https://codesandbox.io/s/strange-kapitsa-zbtok?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.vue&theme=dark
You may want to look into Render Function, jsx is also supported in Vue.js2.
Here's a simple example.
var element = 'ol'
Vue.component('custom', {
render: function (createElement) {
return createElement(
element,
this.$slots.default
)
},
})
new Vue({
el: "#app"
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<custom>abc</custom>
</div>

How to wrap a Backbone view element with a div

I am facing issues in adding wrapper div on strut Slidesnapshot which uses Backbone.js.
render: function() {
if (this._slideDrawer) {
this._slideDrawer.dispose();
}
this.$el.addClass('testall');
this.$el.wrap('<div class="check"></div>');
this.$el.html(this._template(this.model.attributes));
.addClass added the class on div but I am not able to wrap the html inside parent div.
A Backbone view represents one DOM element, which is accessible with view.el.
Often, a parent view is rendering the child view before putting its element in the DOM. So the child view wraps itself with a div, but then the parent still uses view.el to get the original element.
Though I strongly suggest rethinking the need to wrap a child div, here's a way to accomplish it with Backbone:
var Child = Backbone.View.extend({
template: "<span>This is the template</span>",
render: function() {
// create a wrapper
var $wrap = $('<div class="check"></div>');
// keep a reference to the original element
this.$innerEl = (this.$innerEl || this.$el).addClass('testall')
.html(this.template);
// wrap the inner element.
$wrap.html(this.$innerEl);
// then replace the view el.
this.setElement($wrap);
return this;
}
});
var Parent = Backbone.View.extend({
el: '#app',
initialize: function() {
this.child = new Child();
},
render: function() {
this.$el.html(this.child.render().el);
return this;
}
});
var parent = new Parent();
parent.render();
parent.render(); // make sure it's idempotent
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<div id="app"></div>
Inspect the result with dev tools to see the wrapping div.
The right way to go about this would be to customize the view element as your wrapper, and adding a child <div> with required classes (current view element) to the template if required. For example:
var V = new Backbone.View.extend({
className: 'check',
render: function(){}
});
This will result in the same DOM structure. You can customize the view element as you wish using View-attributes. This will keep the code more maintainable and less prone to bugs in future. I don't see a reason to hack around.

How execute a oncomplete script for many divs based on class selector?

I've a webpage that uses Ractive js & tries to execute a script for element's with a particular class. Please find the below sample:
<body>
<div class='container'></div>
<div class='container'>Hello</div>
<script>
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.container',
oncomplete: function () {
console.log("22");
}
});
</script>
</body>
For the above code, I can see only one entry in the console. Why is this the case? How do I get ractive to execute the oncomplete script for all elements having container as class?
The problem is that you can't really initiate Ractive with multiple elements. The idea is that you initialize Ractive on the wrapper and then you build up your page with partials.
If you for some reason want to do what you are proposing, you can create multiple Ractive instances on one page like this: http://jsfiddle.net/7yxnd5wb/1/
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.c1',
template: 'rendered 1',
onrender: function () {
console.log("11");
}
})
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.c2',
template: 'rendered 2',
onrender: function () {
console.log("22");
}
})
I believe the option to use css class selector is a new one and might be confusing. Most people seem to be using ids for this use.
If you really want to create many ractive instances based on class, consider this function: http://jsfiddle.net/7yxnd5wb/2/
function ractive_class (class_selector) {
return $('.' + class_selector)
.toArray()
.map(function (element) {
return new Ractive({
el: element,
template: 'rendered',
onrender: function () {
console.log("rendered");
}
}
)
})
}
console.log(ractive_class('c'))

DOM element to corresponding vue.js component

How can I find the vue.js component corresponding to a DOM element?
If I have
element = document.getElementById(id);
Is there a vue method equivalent to the jQuery
$(element)
Just by this (in your method in "methods"):
element = this.$el;
:)
The proper way to do with would be to use the v-el directive to give it a reference. Then you can do this.$$[reference].
Update for vue 2
In Vue 2 refs are used for both elements and components: http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced
In Vue.js 2 Inside a Vue Instance or Component:
Use this.$el to get the HTMLElement the instance/component was mounted to
From an HTMLElement:
Use .__vue__ from the HTMLElement
E.g. var vueInstance = document.getElementById('app').__vue__;
Having a VNode in a variable called vnode you can:
use vnode.elm to get the element that VNode was rendered to
use vnode.context to get the VueComponent instance that VNode's component was declared (this usually returns the parent component, but may surprise you when using slots.
use vnode.componentInstance to get the Actual VueComponent instance that VNode is about
Source, literally: vue/flow/vnode.js.
Runnable Demo:
Vue.config.productionTip = false; // disable developer version warning
console.log('-------------------')
Vue.component('my-component', {
template: `<input>`,
mounted: function() {
console.log('[my-component] is mounted at element:', this.$el);
}
});
Vue.directive('customdirective', {
bind: function (el, binding, vnode) {
console.log('[DIRECTIVE] My Element is:', vnode.elm);
console.log('[DIRECTIVE] My componentInstance is:', vnode.componentInstance);
console.log('[DIRECTIVE] My context is:', vnode.context);
// some properties, such as $el, may take an extra tick to be set, thus you need to...
Vue.nextTick(() => console.log('[DIRECTIVE][AFTER TICK] My context is:', vnode.context.$el))
}
})
new Vue({
el: '#app',
mounted: function() {
console.log('[ROOT] This Vue instance is mounted at element:', this.$el);
console.log('[ROOT] From the element to the Vue instance:', document.getElementById('app').__vue__);
console.log('[ROOT] Vue component instance of my-component:', document.querySelector('input').__vue__);
}
})
<script src="https://unpkg.com/vue#2.5.15/dist/vue.min.js"></script>
<h1>Open the browser's console</h1>
<div id="app">
<my-component v-customdirective=""></my-component>
</div>
If you're starting with a DOM element, check for a __vue__ property on that element. Any Vue View Models (components, VMs created by v-repeat usage) will have this property.
You can use the "Inspect Element" feature in your browsers developer console (at least in Firefox and Chrome) to view the DOM properties.
Hope that helps!
this.$el - points to the root element of the component
this.$refs.<ref name> + <div ref="<ref name>" ... - points to nested element
💡 use $el/$refs only after mounted() step of vue lifecycle
<template>
<div>
root element
<div ref="childElement">child element</div>
</div>
</template>
<script>
export default {
mounted() {
let rootElement = this.$el;
let childElement = this.$refs.childElement;
console.log(rootElement);
console.log(childElement);
}
}
</script>
<style scoped>
</style>
So I figured $0.__vue__ doesn't work very well with HOCs (high order components).
// ListItem.vue
<template>
<vm-product-item/>
<template>
From the template above, if you have ListItem component, that has ProductItem as it's root, and you try $0.__vue__ in console the result unexpectedly would be the ListItem instance.
Here I got a solution to select the lowest level component (ProductItem in this case).
Plugin
// DomNodeToComponent.js
export default {
install: (Vue, options) => {
Vue.mixin({
mounted () {
this.$el.__vueComponent__ = this
},
})
},
}
Install
import DomNodeToComponent from'./plugins/DomNodeToComponent/DomNodeToComponent'
Vue.use(DomNodeToComponent)
Use
In browser console click on dom element.
Type $0.__vueComponent__.
Do whatever you want with component. Access data. Do changes. Run exposed methods from e2e.
Bonus feature
If you want more, you can just use $0.__vue__.$parent. Meaning if 3 components share the same dom node, you'll have to write $0.__vue__.$parent.$parent to get the main component. This approach is less laconic, but gives better control.
Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for.
For example:
<ul>
<li v-for="(item, key) in items" v-on:click="play(item,$event)">
<a v-bind:ref="'key' + item.id" v-bind:href="item.url">
<!-- content -->
</a>
</li>
</ul>
and in Vue component you can use
var recordingModel = new Vue({
el:'#rec-container',
data:{
items:[]
},
methods:{
play:function(item,e){
// it contains the bound reference
console.log(this.$refs['key'+item.id]);
}
}
});
I found this snippet here. The idea is to go up the DOM node hierarchy until a __vue__ property is found.
function getVueFromElement(el) {
while (el) {
if (el.__vue__) {
return el.__vue__
} else {
el = el.parentNode
}
}
}
In Chrome:
Solution for Vue 3
I needed to create a navbar and collapse the menu item when clicked outside. I created a click listener on windows in mounted life cycle hook as follows
mounted() {
window.addEventListener('click', (e)=>{
if(e.target !== this.$el)
this.showChild = false;
})
}
You can also check if the element is child of this.$el. However, in my case the children were all links and this didn't matter much.
If you want listen an event (i.e OnClick) on an input with "demo" id, you can use:
new Vue({
el: '#demo',
data: {
n: 0
},
methods: {
onClick: function (e) {
console.log(e.target.tagName) // "A"
console.log(e.targetVM === this) // true
}
}
})
Exactly what Kamil said,
element = this.$el
But make sure you don't have fragment instances.
Since in Vue 2.0, no solution seems available, a clean solution that I found is to create a vue-id attribute, and also set it on the template. Then on created and beforeDestroy lifecycle these instances are updated on the global object.
Basically:
created: function() {
this._id = generateUid();
globalRepo[this._id] = this;
},
beforeDestroy: function() {
delete globalRepo[this._id]
},
data: function() {
return {
vueId: this._id
}
}

Marionette.js - can I detect onAppend?

I have a silly problem, where my only solution is a sloppy hack that is now giving me other problems.
See my fiddle,
or read the code here:
HTML:
<input id='1' value='input1' />
<template id='template1'>
<input id='2' value='input2' />
</template>
JS - Item View Declaration:
// Declare an ItemView, a simple input template.
var Input2 = Marionette.ItemView.extend({
template: '#template1',
onRender: function () {
console.log('hi');
},
ui: { input2: '#2' },
onRender: function () {
var self = this;
// Despite not being in the DOM yet, you can reference
// the input, through the 'this' command, as the
// input is a logical child of the ItemView.
this.ui.input2.val('this works');
// However, you can not call focus(), as it
// must be part of the DOM.
this.ui.input2.focus();
// So, I have had to resort to this hack, which
// TOTALLY SUCKS.
setTimeout(function(){
self.ui.input2.focus();
self.ui.input2.val('Now it focused. Dammit');
}, 1000)
},
})
JS - Controller
// To start, we focus input 1. This works.
$('#1').focus();
// Now, we make input 2.
var input2 = new Input2();
// Now we 1. render, (2. onRender is called), 3. append it to the DOM.
$(document.body).append(input2.render().el);
As one can see above, my problem is that I can not make a View call focus on itself after it is rendered (onRender), as it has not yet been appended to the DOM. As far as I know, there is no other event called such as onAppend, that would let me detect when it has actually been appended to the DOM.
I don't want to call focus from outside of the ItemView. It has to be done from within for my purposes.
Any bright ideas?
UPDATE
Turns out that onShow() is called on all DOM appends in Marionette.js, be it CollectionView, CompositeView or Region, and it isn't in the documentation!
Thanks a million, lukaszfiszer.
The solution is to render your ItemView inside a Marionette.Region. This way an onShow method will be called on the view once it's inserted in the DOM.
Example:
HTML
<input id='1' value='input1' />
<div id="inputRegion"></div>
<template id='template1'>
<input id='2' value='input2' />
</template>
JS ItemView
(...)
onShow: function () {
this.ui.input2.val('this works');
this.ui.input2.focus();
},
(...)
JS Controller
$('#1').focus();
var inputRegion = new Backbone.Marionette.Region({
el: "#inputRegion"
});
var input2 = new Input2();
inputRegion.show(input2);
More information in Marionette docs: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#region-events-and-callbacks
Well, I managed to solve it by extending Marionette.js, but if anyone else has a better idea that doesn't involve extending a library, I will GLADLY accept it and buy you a doughnut.
// After studying Marionette.js' annotated source code,
// I found these three functions are the only places
// where a view is appended after rendering. Extending
// these by adding an onAppend call to the end of
// each lets me focus and do other DOM manipulation in
// the ItemView or Region, once I am certain it is in
// the DOM.
_.extend(Marionette.CollectionView.prototype, {
appendHtml: function(collectionView, itemView, index){
collectionView.$el.append(itemView.el);
if (itemView.onAppend) { itemView.onAppend() }
},
});
_.extend(Marionette.CompositeView.prototype, {
appendHtml: function(cv, iv, index){
var $container = this.getItemViewContainer(cv);
$container.append(iv.el);
if (itemView.onAppend) { itemView.onAppend() }
},
});
_.extend(Marionette.Region.prototype, {
open: function(view){
this.$el.empty().append(view.el);
if (view.onAppend) { view.onAppend() }
},
});

Categories