How to run method when a div re-init - Vuejs - javascript

I try
<div v-if="isRun" v-html="myMethod"></div>
<div v-else v-html="myMethod2"></div>
computed: {
myMethod: function() {
alert('1');
},
myMethod2: function() {
alert('2');
},
}
and isRun variable's change dynamic (switch by user) and i want myMethod and myMethod2 re-run when div re-init.
But I try with v-html it only run first time.
How to do that. thank.

So if you are strictly want to use this methods, computed properties watch their dependencies for changes before they get updated, from the docs
You can data-bind to computed properties in templates just like a normal property. Vue is aware that vm.reversedMessage depends on vm.message, so it will update any bindings that depend on vm.reversedMessage when vm.message changes. And the best part is that we’ve created this dependency relationship declaratively: the computed getter function has no side effects, which makes it easier to test and understand.
Here is a simple hack for your needs, there is better ways for this approach
new Vue({
el: "#app",
data:{
isRun: true,
},
computed: {
myMethod: function() {
this.isRun == this.isRun // it doesn't really depend on this.isRun but vue thinks it does
alert('1');
},
myMethod2: function() {
this.isRun == this.isRun
alert('2');
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<div v-if="isRun" v-html="myMethod"></div>
<div v-else v-html="myMethod2"></div>
<button #click="isRun = !isRun">Toggle</button>
</div>

Related

Is there any way to have multiple Vues have a computed listener working on the same value?

Setup:
I have multiple Vue components, and each component has multiple instances in different dialogs in my web app.
For each type of component I have a global state (handrailOptions in the example below) so that each type of component stays in sync across the dialogs.
I'd like for it so that when a component proceeds beyond step 1, I hide the other components in that dialog.
I have achieved this nicely using the computed / watch combo.
However, my problem is that it seems if I try to listen in with computed through more than 1 Vue instance, it hijacks the other listeners.
Problem
Below is a simplified version of what I'm working with, when the app starts up, the console logs 'computed 1' & 'computed 2'. But then when I change handrailOptions.step, only the second fires. ('computed 2' & 'watched 2')
Is there any way to have multiple Vues have a computed listener working on the same value?
handrailOptions = {
step: 1
};
Vue.component( 'handrail-options', {
template: '#module-handrail-options',
data: function() {
return handrailOptions;
},
});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 2');
}
}
});
This works as expected. I made handrailOptions responsive by making the data object of a new Vue. Making it the data object of a component, as you did, could also work, but the component would have to be instantiated at least once. It makes more sense to have a single object for your global, anyway.
handrailOptions = {
step: 1
};
// Make it responsive
new Vue({data: handrailOptions});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 2');
}
}
});
setInterval(() => ++handrailOptions.step, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="dialog-estimate-questions">
Main step {{newHandrailStep}}
</div>
<div id="dialog-checkout">
CD step {{newHandrailStep}}
</div>

Using Masonry.JS and Vue.JS

I know how to use masonry.js apart from vue. However, I'm having issue getting it to function and be called correctly inside of the vue framework. I called it inside of the created or ready but neither seem to get the grid to form correctly. How can i get this to work inside the framework? Oh and I do have jquery called in the html before this script. Here is what I have inside the component :
Edit:
I can see that the masonry is effecting the grid by assigning its height with JS and changing the items to position absolute. However, its not placing them correctly. Its stacking them ontop eachother instead of sideby side like it should be in the grid.
<template>
<div class="projects--container">
<div class="inner-section inner--options">
<div class="grid">
<div class="grid-item"></div>
<div class="grid-item"></div>
<div class="grid-item"></div>
</div>
</div>
</div>
</template>
<script>
export default{
ready: function () {
this.mason();
},
data: function () {
return {
options: [
{
option: 'projects',
phrase: 'for clients',
slogan: 'slogan...'
},
{
option: 'sides',
phrase: 'for us',
slogan: 'we love what we make'
},
{
option: 'moments',
phrase: 'with the crew'
}
]
}
},
methods: {
revert: function () {
this.$dispatch('return-home', true)
},
mason: function () {
var $grid = $('.grid').masonry({
itemSelector: '.grid-item',
columnWidth: 250
});
$grid.masonry('layout');
}
},
events: {
'option-select': function (option) {
}
}
}
</script>
As I saw it, most mv* frameworks like vue keep DOM elements (view) in sync with js (model), in the other hand, frameworks like masonry just need valid DOM to work.
So, the tricky part is to tell one to another when DOM has changed.
So the first change is when vue finished to render all DOM, as mentioned in other answers, we are notified on mounted lifecycle hook, here is where we can initialize masonry
mounted() {
let grid = document.querySelector('.grid');
this.msnry = new Masonry(grid, {
columnWidth: 25
});
},
In any other change to our view need also update masonry as well, if you change items size use layout() method, if you add or remove items use reloadItems() method
methods: {
toggle(item) {
item.isGigante = !item.isGigante;
Vue.nextTick(() => {
// DOM updated
this.msnry.layout();
});
},
add() {
this.items.push({
isGigante: false,
size: '' + widthClasses[Math.floor(Math.random() * widthClasses.length)] + ' ' + heightClasses[Math.floor(Math.random() * heightClasses.length)]
});
Vue.nextTick(() => {
// DOM updated
this.msnry.reloadItems();
this.msnry.layout();
});
}
}
Please note that those methods are called after vue has completed DOM update using Vue.nextTick function.
Here is a working fiddle.
I guess the vue-way of doing this is by using refs. Just assign a ref property to your html element inside the template and access it using the vm.$ref instance property inside the mounted callback.
A sample code may look like this:
<template>
<div class="grid" ref="grid">
<div class="grid-item"></div>
<div class="grid-item"></div>
<div class="grid-item"></div>
</div>
</template>
<script>
import Masonry from "masonry"; // or maybe use global scoped variable here
export default {
mounted: function(){
let $masonry = new Masonry(this.$refs.grid, {
// masonry options go in here
// see https://masonry.desandro.com/#initialize-with-vanilla-javascript
});
}
}
</script>
In Vue2, there is no such thing as a ready lifecycle hook. Instead, the mounted lifecycle hook is triggered once the instance is "ready" in the way you think of.
Reference: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
It could be that the vertical stack just indicates that masonry is not working (It's hard to tell without a codepen/plunkr). #riyaz-ali has the right idea though.
you must call masonry inside mounted() event to make it work.
i am using this on my project (with imagesloaded) its work perfectly
massonryApply (container, context, selector) {
container = $(`#${container}`)
const $grid = container.imagesLoaded(function () {
$grid.masonry({
itemSelector: `.${selector}`,
percentPosition: true,
columnWidth: `.${selector}`
})
$grid.masonry('reloadItems')
})
}

Listen to custom event in Vue.js

Vue.js works great with browser events such as click or mousedown. But not work at all with custom events. Here is the code:
HTML:
<div id="app" style="display: none" v-show="true">
<div v-el:ping v-on:ping="ping">
<div>
<button v-on:click="click">Click</button>
</div>
</div>
</div>
JavaScript:
new Vue({
el: '#app',
data: {
},
methods: {
ping: function (event) {
console.log('Vue ping', event);
alert('Vue ping');
},
click: function (event) {
jQuery(event.target).trigger('ping');
}
},
ready: function () {
console.log(this.$els);
jQuery(this.$els.ping).on('ping', function (event) {
console.log('jQuery ping', event);
alert('jQuery ping');
});
}
});
I expect alert with Vue ping and jQuery ping. But only the later pops up.
CodePen
Vue has its own internal system for custom events, which you should use instead of jQuery / native DOM events:
click: function (event) {
// jQuery(event.target).trigger('ping');
this.$dispatch('ping', event.target) // send event up the parent chain, optionally send along a reference to the element.
// or:
this.$emit('ping') // trigger event on the current instance
}
Edit: $dispatch is for parent-child communication, You seem to want to trigger a custom event from within the same comonent. In that case, you could instead simply call a method.
If you still want to listen to a custom event inside the same component, you:
want to use $emit
cannot use v-on:custom-event-name in the template (that's only to be used on components). Rather, add the event method to the events::
events: {
ping: function() {....}
}
Here it is in vanilla JS:
HTML:
<div id="app">
<div v-el:ping>
<div>
<button v-on:click="click">Click</button>
</div>
</div>
</div>
JS:
(function() {
new Vue({
el: '#app',
data: {
event: null
},
methods: {
ping: function(event) {
alert('Vue ping');
},
click: function(event) {
this.$els.ping.dispatchEvent(this.event);
}
},
ready: function() {
this.event = document.createEvent("HTMLEvents");
this.event.initEvent("ping", true, true);
this.$els.ping.addEventListener('ping', this.ping);
}
});
})();
pen: http://codepen.io/anon/pen/wGdvaV?editors=1010#0
You should avoid to mix a dom events and vue-components related ones because it's a different layers of abstraction.
Anyway, if you still want to do that, I think you need to cache this.el inside a vue-component instance or take it via computed-property like this
{
computed : {
jqueryEl(){ return $(this.el) }
}
}
And then trigger a custom jQuery events by this.jqueryEl.trigger('ping').
Sure to properly take care of keep the element's bindings up to date!
For example you can bind jQuery events dynamically (and also unbind on component destroy!) like this:
ready : function(){
jQuery('body').on('ping.namespace', '[data-jquery="ping"]', function(){ ... })
},
destroy : function(){
jQuery('body').off('ping.namespace')
}
And don't forget to add attribute [data-jquery="ping"] to an element which you would like to response a ping event.
Hope this information helps you to achieve the expected result.

VueJS and tinyMCE, custom directives

I've been struggling hard with getting VueJS and TinyMCE to work together. I've come to the conclusion that using directives would be the way to go.
So far I've been able to pass in the body as a directive parameter, and tinyMCE sets the content. However, I can't get the two way binding to work. I'm also afraid that I'm doing things completely wrong based on the tinyMCE api.
The relevant tinyMCE functions I assume would be:
http://community.tinymce.com/wiki.php/api4:method.tinymce.Editor.setContent
// Sets the content of a specific editor (my_editor in this example)
tinymce.get('my_editor').setContent(data);
and
http://community.tinymce.com/wiki.php/api4:method.tinymce.Editor.getContent
// Get content of a specific editor:
tinymce.get('content id').getContent()
HTML
<div id="app">
<h3>This is the tinyMCE editor</h3>
<textarea id="editor" v-editor :body="body"></textarea>
<hr>
<p>This input field is properly binded</p>
<input v-model="body">
<hr>
<pre>data binding: {{ body }} </pre>
</div>
JS
tinymce.init({
selector:'#editor',
});
Vue.directive('editor', {
twoWay: true,
params: ['body'],
bind: function () {
tinyMCE.get('editor').setContent(this.params.body);
tinyMCE.get('editor').on('change', function(e) {
alert("changed");
});
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
});
var editor = new Vue({
el: '#app',
data: {
body: 'The message'
}
})
Fiddle
https://jsfiddle.net/nf3ftm8f/
With Vue.js 2.0, the directives are only used for applying low-level direct DOM manipulations. They don't have this reference to Vue instance data anymore. (Ref: https://v2.vuejs.org/v2/guide/migration.html#Custom-Directives-simplified)
Hence I recommend to use Component instead.
TinymceComponent:
// Use JSPM to load dependencies: vue.js 2.1.4, tinymce: 4.5.0
import Vue from 'vue/dist/vue';
import tinymce from 'tinymce';
// Local component
var TinymceComponent = {
template: `<textarea class="form-control">{{ initValue }}</textarea>`,
props: [ 'initValue', 'disabled' ],
mounted: function() {
var vm = this,
tinymceDict = '/lib/jspm_packages/github/tinymce/tinymce-dist#4.5.1/';
// Init tinymce
tinymce.init({
selector: '#' + vm.$el.id,
menubar: false,
toolbar: 'bold italic underline | bullist numlist',
theme_url: tinymceDict + 'themes/modern/theme.js,
skin_url: tinymceDict + 'skins/lightgray',
setup: function(editor) {
// If the Vue model is disabled, we want to set the Tinymce readonly
editor.settings.readonly = vm.disabled;
if (!vm.disabled) {
editor.on('blur', function() {
var newContent = editor.getContent();
// Fire an event to let its parent know
vm.$emit('content-updated', newContent);
});
}
}
});
},
updated: function() {
// Since we're using Ajax to load data, hence we have to use this hook because when parent's data got loaded, it will fire this hook.
// Depends on your use case, you might not need this
var vm = this;
if (vm.initValue) {
var editor = tinymce.get(vm.$el.id);
editor.setContent(vm.initValue);
}
}
};
// Vue instance
new Vue({
......
components: {
'tinymce': TinymceComponent
}
......
});
Vue Instance (simplified)
new Vue({
el: '#some-id',
data: {
......
description: null
......
},
components: {
'tinymce': TinymceComponent
},
methods: {
......
updateDescription: function(newContent) {
this.description = newContent;
},
load: function() {
......
this.description = "Oh yeah";
......
}
......
},
mounted: function() {
this.load();
}
});
HTML (MVC view)
<form id="some-id">
......
<div class="form-group">
<tinymce :init-value="description"
v-on:content-updated="updateDescription"
:id="description-tinymce"
:disabled="false">
</tinymce>
</div>
......
</form>
The flows
First the data is loaded through remote resources, i.e., AJAX. The description got set.
The description got passed down to the component via props: initValue.
When the component is mounted, the tinymce is initialized with the initial description.
It also sets up the on blur event to get the updated content.
Whenever the user loses focus on the editor, a new content is captured and the component emits an event content-updated, letting the parent know that something has happened.
On Html you have v-on:content-updated. Since the parent is listening to the content-updated event, the parent method updateDescription will be called when the event is emited.
!!Couple Important Notes!!
By design, the component has 1 way binding, from parent to component. So when the description gets updated from Vue instance, the component's initValue property should be updated as well, automatically.
It would be nice if we can pass whatever the user types in tinymce editor back to the parent Vue instance but 2 ways bindings is not supposed. That's when you need to use $emit to fire up events and notify parents from components.
You don't have to define a function in parent and do v-on:content-updated="updateDescription". You can just directly update the data by doing v-on:content-updated="description = $event". The $event has the parameter you defined for the function inside the component - the newContent parameter.
Hope I explained things clearly. This whole thing took me 2 weeks to figure it out!!
Here's a Tinymce component for Vue.
http://jsbin.com/pucubol/edit?html,js,output
It's also good to know about v-model and custom input components:
https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
Vue.component('tinymce', {
props: ['value'],
template: `<div><textarea rows="10" v-bind:value="value"></textarea></div>`,
methods: {
updateValue: function (value) {
console.log(value);
this.$emit('input', value.trim());
}
},
mounted: function(){
var component = this;
tinymce.init({
target: this.$el.children[0],
setup: function (editor) {
editor.on('Change', function (e) {
component.updateValue(editor.getContent());
})
}
});
}
});
<tinymce v-model="whatever"></tinymce>
Try this:
Vue.directive('editor', {
twoWay: true,
params: ['body'],
bind: function () {
tinyMCE.get('editor').setContent(this.params.body);
var that = this;
tinyMCE.get('editor').on('change', function(e) {
that.vm.body = this.getContent();
});
}
});
The trick was storing the directive in the temporary variable "that" so you could access it from within the change event callback.
There is now an npm package which is a thin wrapper around TinyMCE, making it easier to use in a Vue application.
It is open source with code on GitHub.
Installation:
$ npm install #tinymce/tinymce-vue
Usage:
import Editor from '#tinymce/tinyme-vue';
Templates:
<editor api-key="API_KEY" :init="{plugins: 'wordcount'}"></editor>
Where API_KEY is your API key from tiny. The init section is the same as the default init statement except you do not need the selector. For an example see the documentation.

Backbone LayoutManager Re-Render SubViews

I'm using BBB with the great LayoutManager for the views.
Unfortunately, i can't find a way to re-render specific subviews. Here is my setting:
Home.Views.Layout = Backbone.Layout.extend({
template: "home/home",
el: "#main",
views: {
"#left-menu-container": new Home.Views.Leftmenu(),
"#searchbox": new Home.Views.Searchbox(),
"#content": new Home.Views.Content()
}
});
Home.HomeView = new Home.Views.Layout();
Home.HomeView.render();
Home.Views.AddEditPatient = Backbone.View.extend({
template: "......",
events: {
'click .dosomething': 'dosomething'
},
dosomething: function(){
// [dosomething]
// Only Render Sub-View, e.g. #content here...
}
});
I don't want to re-render the whole layout, what would be possible by calling Home.HomeView.render() again, but how can i render only the sub-view in this setting?
I think you want to add to do something like this with backbone.layoutmanager
thisLayout.setView("#content", new View()).render();
The backbone.layoutmanager v0.6.6 documentation might be helpful
http://documentup.com/tbranyen/backbone.layoutmanager/#usage/nested-views
Also check
http://vimeo.com/32765088
If I understand your question correctly, you can do this in your dosomething function:
this.$("#divToRenderTo").html(new subView().render().$el);
Be sure to have "return this;" at the end of your sub-view's render function.
There are two ways I generally do this with layoutmanager:
Instantiate views in your initialize function and then drop them into the view in beforeRender. This gives your view access to the subview so you can render it directly.
initialize: function() {
this.subview = new SubView();
},
beforeRender: function() {
this.insertView(this.subview);
},
doSomething: function() {
this.subview.render();
}
You can use view.getView(#selector) to return the embedded view and then call render on that.
doSomething: function() {
this.getView('#content').render();
}

Categories