vue: design a dynamic "window" component like bootstrap modal - vue.js

I am trying to create a generic HTML Window from Vue application.
What I want
I click "edit" on item from list, a DHTML popup will be opened with a Vue component inside. I can click Save or close the opened component need to removed from the page and may the caller recive a event/promise with resolve (Save) or reject (Close).
What I am tried
I was started via DOM:
function Win(vueapp,obj) {
var div = document.createElement("div");
var Win = JQueryOldFunction()
//need a way to resolve when the user hit 'Save' and reject when closing
var resolve,reject, promise = new Promise(function(a, b) {
resolve=a, reject=b;
});
new Vue({
el: div,
data: {data:obj} //need a way to pass it to the component
render: function (h) {
return h("m");
},
components: {
'm': httpVueLoader(vueapp+'.vue') // this from https://github.com/FranckFreiburger/http-vue-loader
}
});
return promise;
}
This is working, but I can't use Vue to destroy the window after clicking save, and can't pass objects inside
so I was tried other option:
<component :is="gui.window" transition="fade" transition-mode="out-in"></component>
Model.gui.window='EditGroup'
(took from here: https://coligo.io/dynamic-components-in-vuejs/ )
This is better, it working, does not allowing more than one window, but the internal component can't destroy itself.

Related

How to close a nativescript modal after a few navigations?

A modal is opened, which has multiple pages. For the navigation I'm using frame navigation. There is a close button on every page clicking on which closes the modal.
What I'm doing now is passing this.$modal as a property to each page which creates a long chain of property passing and on each page I just do this.modal.close() where this.modal is the property of the component that refers to the this.$modal of the first page.
I was wondering if there was a better way, such as accessing the topmost open modal and closing it.
I'm using nativescript-vue and the builtin nativescript modals
Please note that I have multiple modals in other parts of my application. there is only this one that has navigation in it.
A small improvement could be saving the modal into the Vuex store, accessing it anytime instead of chaining props.
Detach modal component by plugin.
const modalDialog = {
install (Vue, options = {}) {
// ...
}
}
Vue.use(modalDialog)
Designate Vue prototype for plugin.
const modalDialog = {
install (Vue, options = {}) {
Vue.prototype.$modal = {
show () {
// ...
},
hide () {
// ..
}
}
}
}
Vue.use(modalDialog)
this.$modal is accessible from all components.
this.$modal.show() // or hide()

How can a dynamically instantiated Vue Component be cached?

In my application I instantiate a dialog component programmatically. The dialog can (does) contain a child component to show content. I accomplish this thus:
// Create the containing dialog component.
// I don't care if this is cached or not, and it's easy
// to recreate.
//
var DialogComponent = Vue.extend(Dialog);
var instance = new DialogComponent({
parent: parent,
data: {...}
propsData: {...}
});
// Compile the subcomponent string. In practice this would
// be built dynamically before being passed to
// Vue.compile()
//
const template = '<ComponentName :binding="" ... />'
const x = Vue.compile(template);
const slotContent = {
data() { return data},
render: x.render,
staticRenderFns: x.staticRenderFns
}
// Create a vnode from the compiled render functions. This
// is the part I am less confident in, as it appears the
// render function returned will always instantiate a new
// child component, and it feels like involving `instance`,
// i.e. the dialog, is incorrect.
//
const vnode = instance.$createElement(slotContent);
instance.$slots.default = [ vnode ];
// Mount the dialog component to a DOM element.
//
var tmp = document.createElement('div');
document.body.appendChild(tmp);
instance.$mount(tmp);
// Hook the dialog close event to clean up the instance.
// In practice, when caching I would like to pull the
// child component out and only destroy the dialog instance.
// I can successfully set the child's vnode.data.keepAlive and
// persist the instance, but a new one gets created nonetheless.
//
instance.$on('close', (e) => {
instance.$el.parentNode.removeChild(instance.$el);
instance.$destroy();
})
I believe this is the accepted way of accomplishing this, and it works flawlessly. However, each time the dialog goes away it is of course $destroyed and the component resets when it's shown again.
I now need a method of maintaining the subcomponent's state when the dialog closes and reopens. So imagine a dialog that shows a new Date() on creation— I need that same date to show up after the dialog is closed and reopened. I have tried various sprinklings of <keep-alive> to no avail, but I don't think this is the appropriate use of that component.
Caching the vnode successfully avoids the compile call, but because (I think) the returned render function instantiates a component instance (ComponentName in the example) it does not reuse the original ComponentName, even if I successfully avoid destroying it (vm._isDestroyed == false).
Ultimately I think I'd like to hit line 73 in vue/create-component.js when inserting the child component, but a breakpoint there never gets hit (which may be an unrelated webpack/source-maps thing).
Is there a sane way of accomplishing the caching of a programmatically instantiated Vue component for later reuse similar to how <keep-alive> works?

Vue loses prototypes and imported files when creating new component

When extending a Vue component to a modal, a new Vue instance is generated. This leads to losing all pre-imported helpers/ Vuex-stores etc.
Is there a way how to copy all data to the new extended Vue component?
To make it clear, have a look at this jsfiddle.
It confronts you with an alert. When you click on the button, the modal is opened. But if you look at my code, another alert should be fired. But $hi() is not available in the other Vue instance.
I create a component like this:
const createComponent = (props) => {
const wrapper = document.createElement('div');
const Component = Vue.extend(MyComponent);
return new Component({
propsData: {
props,
},
}).$mount(wrapper);
};
I'm using SweetAlert as a modal.
So I add this like:
swal({
content: createComponent(props).$el,
})
If you move the line
Vue.prototype.$hi = () => alert('hi');
above
let component = new TestComponent().$mount(wrapper)
it will work. Your first component references $hi() before it's defined.

How i can use a router-link in kendo grid (vuejs)

How i can use <router-link to="/detail/1">Details</router-link> with Kendo Grid?
I want use the standard logic of a normal link in the grid, with no reload of the full page on click. I have try to use router-link inside the grid with a template but it's doesn't work. Also no error in available in console.
Example on Stackblitz
Update 24 October 2018
Kendo Grid for Vue is initializing a plain Kendo Grid widget for jQuery.
Thus, all templates are evaluated runtime and possible routes cannot be evaluated afterwards or the router-link elements to be resolved.
If you have the same problem Vote for this suggestion
vue native implementation
It is true that you cannot use router-link inside a kendo template. But I thought I would offer an alternative way to achieve the same result.
You can redirect to the new page using the programmatic way by calling router.push({ path:/detail/${id}}).
Replace the template with a command that implements a click event, and call route.push from there.
I have updated your example on slackblitz with a working example.
Kendo Grid with command col
<kendo-grid ref="grid"
:height="550"
:data-source-ref="'datasource1'"
:sortable="true">
<kendo-grid-column :field="'ProductID'"
:title="'Product ID'"></kendo-grid-column>
<kendo-grid-column :field="'ProductName'"
:title="'Product Name'"></kendo-grid-column>
<kendo-grid-column :field="'Link'" :template="linkTemplate1"></kendo-grid-column>
<kendo-grid-column :command="detailsLink"></kendo-grid-column>
</kendo-grid>
Command array in the data object.
new Vue({
el: '#vueapp',
router,
data () {
return {
linkTemplate1: "<a href='/detail/#:ProductID#' class='k-button'>Link</a>",
detailsLink: [{
name: "details",
click: function(e) {
// prevent page scroll position change
e.preventDefault();
var tr = $(e.target).closest("tr"); // get the current table row (tr)
// get the data bound to the current table row
var data = this.dataItem(tr);
// redirect to new page
router.push({ path: `/detail/${data.ProductID}` })
},// template must include k-grid-[command name]
template: "<a class='k-button k-grid-details'>TEST</a>"
}]
}
}
})

Open a modal and fetch data from server on click of a button outside the component

I am very new to Vue.js. In fact I just started today.
I have a problem.
Let's say I have button in a table somewhere in dom.
<table>
<tr><td><button v-on:click="showModal">Show</button>
</table>
Now I have a modal box outside of the scope of the button.
This button is inside a component of itself and the modal box has a component of itself too.
I am passing in an id with this button, and what I want to do is:
Fetch the id on button click
Show the record fetched in the modal and then finally perform some action on it
My problem is I am unable to get a method in the Modal component (that does a http request and fetches and renders the data) to trigger by the click event of this button.
The button and the modal has no relationship, they are not parent/child.
In modal component trigger method to fetch data by component ready state:
ready: function() {
this.getAllTheDataYouNeed();
},
You may use another life cycle hook:
https://vuejs.org/guide/instance.html#Lifecycle-Diagram
An option was to add the event broadcast in the common ancestor of both the components.
Like this:
var main = new Vue({
el: 'body',
components: {
zmodal : zmodal,
showhidebtn : showhidebtn,
},
methods: {
showModal: function (currentId) {
this.$broadcast('openModalBox', currentId);
}
}
});
Add an event listener in 'zmodal' and call this function showModal from on click event of 'showhidebtn' component.
It is working but now I have a set of codes outside the components that have to be triggered for this to work.
I wonder if there is a better way to do this.