Scrolling to v-expansion-panel opening - vuejs2

I'm trying to build a mobile small application using v-expansion-panels to display a list.
The idea is that when the user adds a new item in such list it will open the new panel and scroll down to such new panel.
I found a goTo() method in the $vuetify variable, unfortunatly the v-expansion-panels transition (the "opening") take some time and the goTo() won't completely scroll down because of the scrollbar height changes.
So from my understanding I need to detect the end of the transition (enter/afterEnter hook).
Per the vuetifyjs documentation, I could hope to have a "transition" property on my component. (Which isn't the case anyway). But such property is only a string so I can't hook into it.
My other idea is to, somehow, find the transition component and hook into it. Unfortunatly I have trouble understanding el/vnode and the way vuejs is building is tree like the vue-devtool show and I can't get the transition component. When debugging (in the enter hook callback of the transition) it is like the component/el/vnode has a parent but isn't the child of anybody.
Is there a way to do what I'm looking for?
Here is a jsfiddler of what I currently do: https://jsfiddle.net/kdgn80sb/
Basically it is the method I'm defining in the Vue:
methods: {
newAlarm: function() {
const newAlarmPanelIndex = this.alarms.length - 1;
this.alarms.push({title: "New line"});
this.activePanelIndex = newAlarmPanelIndex;
// TODO:
this.$vuetify.goTo(this.$refs.alarmPanels[newAlarmPanelIndex]);
}
}

Firstly you should open manually panel and then use goTo with a timeout.
It works for me, just give some time to a panel to open.
<v-expansion-panels v-model="alarmPanels">
<v-expansion-panel>
<div id="example" />
</v-expansion-panel>
</v-expansion-panels>
this.alarmPanels.push(0); // Use index of expansion-panel
setTimeout(() => {
this.$vuetify.goTo(`#${resultId}`);
}, 500);

Related

Swiper.js bugged: It knows that there are multiples slides but it doesn't let you navigate through them

I have some data coming from an API and I'm putting these data inside a slider using SwiperJS.
take a look at the code here:
As shown in the picture above. Whenever I navigate to web development/software development/UI design I load new data. The problem is, whenever I navigate to get the data, and loop through and display the data using swiper, swiper only shows me the first slide. Note that when I inspected the page I found all the slides there but the swiper for some reason is not displaying them.
here's the code:
I found the answer to my question. I had to create a function to force the rerendering of the swiper component. Here's the code:
forceRerenderSwiper() {
// Remove my-component from the DOM
this.renderComponent = false;
// If you like promises better you can
// also use nextTick this way
this.$nextTick().then(() => {
// Add the component back in
this.renderComponent = true;
});
}
then you just add an if statement when you call the component
and don't forget to set the renderComponent to true when you're ready to render it.

v-navigation-drawer drops into a runaway loop on window resize

First, let me say that the v-navigation-drawer works as intended, i.e.:
On clicking the hamburger menu the TOGGLE_DRAWER mutation is committed, and it toggles open/closed, updating the state.
On window resize it opens/closes at a designated breakpoint
So it works.
BUT the window resize does not properly toggle the mutation and I keep getting a Vuex mutation error when I resize the window:
I understand why I'm getting this error - the $store.state.ui.drawer is being modified outside of the mutator (it's the v-navigation-drawer's v-model):
<v-navigation-drawer
v-model="$store.state.ui.drawer"
app
clipped
>
I get it's bad form to bind the state to the v-model. But when I try to make a drawer computed property with a get() and set() method that properly gets/commits a mutation, the browser crashes (presumably because the set method triggers an endless loop of commits toggling drawer true/false into infinity):
computed: {
drawer: {
get () {
return this.$store.state.ui.drawer
},
set () {
this.$store.commit('TOGGLE_DRAWER') // <--crashes the browser
}
}
}
I've searched endlessly for a solution to this problem. It's bugging me even though it visually appears to be working.
I've considered running the v-navigation-drawer in stateless mode and handling all the window resize events and state updates manually. I've also considered disabling 'Strict' mode in Vuex (which would hide the errors). But the former is a lot more complexity and the latter is a bandaid that costs me debugging insight in development.
This sounds like a perfect candidate for Lodash's debounce function. If you need to stick with using setter/getter while applying this effect, have a look at this post; otherwise, this one for sequential event subscription on any of the lifecycle hooks.
After spending some time with this, I think I have a solution. Wanted to share for anyone else that may be facing the same issue with VNavigationDrawer using Vuex state to control visibility.
The #input event passes a val parameter, which includes the state of the drawer after the window resizes. I created a new action that is called by the below function:
<v-navigation-drawer
:value="$store.state.ui.drawer"
app
clipped
#input="updateDrawer($event)"
>
Here is the action being dispatched:
methods: {
updateDrawer(event) {
if (event !== this.drawer) { // avoids dispatching duplicate actions; checks for unique window resize event
this.$store.dispatch('updateDrawer',event)
}
}
},
And the action commits the new val to the Vuex store.
Basically, the input event is able to watch for updates to the drawer, and subsequently update the drawer state if it's necessary.
You'll also see above that I stubbornly accepted using :value as the docs suggest, even though I think this should be controlled by a v-model.
Seems to be working - with the right events called and the state being updated appropriately.

Making small components on runtime

I am having a problem working with JQuery DataTable. I had to use that plugin since I had no other choice allowed due to my project requirements.
So the problem is that, I am adding rows to DataTable and in the row there's a column with button HTML tag. Now I want to bind an on click handler to the button.
dt.Rows.Add({
column_1_data,
column_2_data,
"<button #click='itsVueTime'>MyButton</button>"
});
Here dt is the DataTable's instance. Now the problem is #click won't work. I understand that its not being rendered by Vue thats why its not working.
Is there a way to bind click event in this condition?
Without knowing more context, I would recommend this way of doing it
In your component with the method you want to use, you can expose the component like this. (I use mounted, but you can use other lifecycle methods too like created)
mounted() {
window.app = this;
}
then you can use
<button onclick="app.holler()">Say Hello</button>
you can also expose just the function you want to use like so
mounted() {
window.itsVueTime = this.itsVueTime;
}

Vuetify and require.js: How do I show a dynamic component?

I am creating a tab component that loads its v-tab-item components dynamically, given an array of configuration objects that consist of tabName, id, and tabContent which is a resource location for the component. I have it successfully loading the components. However, they don't actually initialize (or run their created() methods) until I switch tabs. I just get empty tabs with the correct labels. Using the DOM inspector initially shows just <componentId></componentId>, and then when I switch tabs, those tags are replaced with all of the component's content.
How do I get the dynamic components to initialize as soon as they are loaded?
EDIT: I created a CodePen here:
https://codepen.io/sgarfio/project/editor/DKgQON
But as this is my first CodePen, I haven't yet figured out how to reference other files in the project (i.e. what to set tabContent to so that require.js can load them up). I'm seeing "Access is denied" in the console, which makes it sound like it found the files but isn't allowed to access them, which is weird because all the files belong to the same project. So my CodePen doesn't even work as well as my actual project. But maybe it will help someone understand what I'm trying to do.
Also, after poking around a bit more, I found this:
http://michaelnthiessen.com/force-re-render/
that says I should change the key on the component and that will force the component to re-render. I also found this:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
Which has a pretty good example of what I'm trying to do, but it doesn't force the async component to initialize in the first place. That's what I need the async components to do - they don't initialize until I switch tabs. In fact they don't even show up in the network calls. Vue is simply generating a placeholder for them.
I got it working! What I ended up doing was to emit an event from the code that loads the async components to indicate that that component was loaded. The listener for that event keeps a count of how many components have been loaded (it already knows how many there should be), and as soon as it receives the right number of these events, it changes the value of this.active (v-model value for the v-tabs component, which indicates which tab is currently active) to "0". I tried this because as I noted before, the async components were loading/rendering whenever I switched tabs. I also have prev/next buttons to set this.active, and today I noticed that if I used the "next" button instead of clicking on a tab, it would load the async components but not advance the tab. I had already figured out how to emit an event from the loading code, so all I had to do at that point was capture the number of loaded components and then manipulate this.active.
I might try to update my CodePen to reflect this, and if I do I'll come back and comment accordingly. For now, here's a sample of what I ended up with. I'm still adding things to make it more robust (e.g. in case the configuration object contains a non-existent component URL), but this is the basic gist of it.
created: function() {
this.$on("componentLoaded", () => {
this.numTabsInitialized++;
if(this.numTabsInitialized == this.numTabs) {
// All tabs loaded; update active to force them to load
this.active = "0";
}
})
},
methods: {
loadComponent: function(config) {
var id = config.id;
var compPath = config.tabContent;
var self = this;
require([compPath], function(comp) {
Vue.component(id, comp);
self.$emit("componentLoaded");
});
}
}

Loading jquery plugin result into Durandal view

I am using the Durandal Starter Template for mvc4. I have set the following simple View:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getrss">Get Posts</button>
<div id="rsstestid" ></div>
</section>
and ViewModel:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
activate: function () {
return;
},
getrss: function () {
$('#rsstestid').rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
return;
}
};
});
As you can see, it is simply using the zRssReader plugin to load posts into a div when the 'Get Posts' button is clicked. Everything works fine, the display name is populated and the posts show up as expected.
Where I am having trouble is when I try to eliminate the button and try to load the posts at creation time. If I place the plugin call in the activate function, I get no results. I assume this is because the view is not fully loaded, so the element doesn't exist. I have two questions:
How do I delay the execution of the plugin call until the view is fully composed?
Even better, how do I load the plugin result into an the posts observable rather than using the query selector? I have tried many combinations but no luck
Thanks for your help.
EDIT** the below answer is for durandal 1.2. In durandal 2.0 viewAttached has changed to attached
Copy pasted directly from durandaljs.com
"Whenever Durandal composes, it also checks your model for a function called viewAttached. If it is present, it will call the function and pass the bound view as a parameter. This allows a controller or presenter to have direct access to the dom sub-tree to which it is bound at a point in time after it is injected into its parent.
Note: If you have set cacheViews:true then viewAttached will only be called the first time the view is shown, on the initial bind, since technically the view is only attached once. If you wish to override this behavior, then set alwaysAttachView:true on your composition binding."
--quoted from the site
There are many ways you can do it but here is just 1 quick and dirty way:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getRss">Get Posts</button>
<div id="rsstestid"></div>
</section>
and the code:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
var $rsstest;
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
viewAttached: function(view) {
$rssTest = $(view).find('#rsstestid');
},
getRss: function() {
$rssTest.rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
}
};
});
In general, I think it's wise to refrain from directly touching UI elements from within your view model.
A good approach is to create a custom KO binding that can render the rss feed. That way, you're guaranteed that the view is in place when the binding executes. You probably want to have the feed url exposed as a property on your view model, then the custom binding can read that when it is being updated.
Custom bindings are pretty simple - if I can do it, then it must be :)
Here's a link to the KnockOut custom bindings quickstart: http://knockoutjs.com/documentation/custom-bindings.html
I too am having the same problem, I'm trying to set a css property directly on an element after the durandal view model and view are bound together. I too assume that it's not working because the view is not fully composed at the point I am setting the value.
Best I have come up with is using the viewAttached lifecycle event in durandal, which I think is the last event in the loading cycle of a durandal viewmodel, and then using setTimeout to delay the setting of the property still further.
It's a pretty rubbish workaround but it's working for now.
var viewAttached = function (view) {
var _this = this;
var picker = new jscolor.color($(view).children('.cp')[0], {
onImmediateChange: function() {
_updateCss.call(_this, this.toString());
}
});
picker.fromString(this.color());
setTimeout(function() {
_updateCss.call(_this, _this.color());
}, 1000);
};
var activate = function (data) {
system.log('activated: ' + this.selectors + ' ' + this.color());
};
var deactivate = function (isClose) {
system.log('deactivated, close:' + isClose);
};
return {
viewAttached: viewAttached,
deactivate: deactivate,
activate: activate,
color: this.color
};
I was having a similar issue with timing. On an initial page load, where a partial view was being loaded on the page I could call the viewAttached function and use jQuery to bind some elements within the partial view. The timing worked as expected
However, if I navigated to a new page, and then back to the initial page, the same viewAttached + jQuery method failed to find the elements on the page... they had not yet been attached to the dom.
As best as I have been able to determine (so far) this is related to the transition effects in the entrance.js file. I was using the default transition which causes an object to fade out and a new object to fade in. By eliminating the fadeOutTransition (setting it to zero in entrance.js) I was able to get the viewAttached function to actually be in sync with the partial views attachment.
Best guess is that while the first object is fading out, the incoming object has not yet been attached to the dom but the viewAttached method is triggered anyway.