Updating parent of recursive component in Vue - recursion

I've made a menu showing systems and their subsystems (can in theory be indefinitely) using a recursive component. A user can both add and delete systems, and the menu should therefore update accordingly.
The menu is constructed using a "tree"-object. This tree is therefore updated when a new system is added, or one deleted. But, I now have a problem; even though the new child component is added when the tree is rerendered, the classes of it's parent-component doesn't update. It is necessary to update this because it defines the menu-element to having children/subsystems, and therefore showing them.
Therefore, when adding a new subsystem, this is presented to the user:
<div class="">
<a href="#/Admin/364" class="item">
<i class=""></i>Testname
<div class=""></div>
</a>
</div>
Instead of this:
<div class="menu transition visible" style="display: block !important;">
<a href="#/Admin/364" class="item">
<i class=""></i>Testname
<div class=""></div>
</a>
</div>
It works fine adding a subsystem to a system which already have subsystems (since the menu-class is already present), but not when a subsystem is added to one without subsystems. In that case, the menu ends up looking like this:
The "opposite" problem also occurs on deletion, since the parent still has the menu-class:
Here's the code for the recursive component:
<template>
<router-link :to="{ name: 'Admin', params: { systemId: id } }" class="item" >
<i :class="{ dropdown: hasChildren, icon: hasChildren }"></i>{{name}}
<div :class="{ menu: hasChildren }">
<systems-menu-sub-menu v-for="child in children" :children="child.children" :name="child.name" :id="child.id" :key="child.id"/>
</div>
</router-link>
</template>
<script type = "text/javascript" >
export default {
props: ['name', 'children', 'id'],
name: 'SystemsMenuSubMenu',
data () {
return {
hasChildren: (this.children.length > 0)
}
}
}
</script>
I'm guessing this has to do with Vue trying to be efficient, and therefore not rerendering everything. Is there therefore any way to force a rerender, or is there any other workaround?
EDIT: JSFiddle https://jsfiddle.net/f6s5qzba/

Related

Vue - requests and watch data on unmounted components

I'm new to vue and strugling with some props and attributes.
I have a vue application where the main app is calling three different components:
Navbar,
Sidebar
MapContainer
In Navbar user will fill a selector with information about city and states. After user presses search, the list of results will then show up in the Sidebar menu.
Sidebar itself is a simple component carrying only a router-view for it's children components which are Results and Details
Results will receive the result of the search performed in the Navbar component. When user clicks any item in the Results component, Sidebar will then load Details taking the place of Results with detailed information about that place.
the problem is that the data used to make the request(city and states) comes from the first component navbar. I'm passing this data to sub-components using vue-router params option. When component Results gets unmounted, I lost all the data that was passed, even adding a Watch couldn'd fix the problem and thus can't return back to the previous page. Even adding a Watch couldn'd fix the problem. What's the proper way to handle data across components that area unmounted?
Navbar.vue
<template>
<div class="navbar">
<div class="logo">
Logo
</div>
<div class="middle">
<div class="flex-selectors">
<div class="navbar-options">
<select v-model="state_id" class="main-selectors" #click="load_cities">
<option v-for="state in states" :value="state.state_id" :key="state">
{{ state.state }}
</option>
</select>
<select v-model="city_id" class="main-selectors">
<option v-for="city in cities" :value="city.city_id" :key="city">
{{ city.city }}
</option>
</select>
</div>
<div class="search">
<button #click="seach">
<router-link :to="{name: 'results', params: {state: state_id, city: city_id} }" aria-current="page" title="Resultados">
<div>Search</div>
</router-link>
</button>
</div>
</div>
</div>
</template>
Sidebar.vue
<template>
<div class="sidebar">
<router-view></router-view>
</div>
</template>
Results.vue
<template>
<div v-if="areas.length">
<router-link :to="{name: 'details' }" tag="div" class="container" #click="load(area)" v-for="area in areas" :key="area" :value="area">
<!-- BUNCH OF DIVS AND V-FOR -->
</router-link>
</div>
<div v-else>
NA
</div>
</template>
<script>
export default {
data() {
return {
state_id: this.$route.params.state,
city_id: this.$route.params.city,
areas: [],
}
},
methods: {
search(state_id, city_id) {
load_areas.get(state_id, city_id).then(
result => {
this.areas = result.data
}
)
}
},
mounted() {
this.emitter = inject('emitter')
this.searchAreas(this.state_id, this.city_id)
},
created() {
this.$watch(
() => this.$route.params,
(toParams, previousParams) => {
this.searchAreas(toParams.state, toParams.city)
}
)
},
watch: {}
}
</script>
What's the proper way to handle data across components that are
unmounted?
You cannot access data from an unmounted component.
When multiple components need to access or modify the same data, a good option to look into is state management. Pinia is the new official library recommendation for state management.
Instead of passing data through vue-router params, create a store for it. Set results of your search query from Navbar component and access it in Results component. When Results component gets unmounted, you won't lose the data.

Angular 2 + Semantic UI , component encapsulation breaks style

I'm using Angular2 with Semantic UI as a css library. I have this piece of code:
<div class="ui three stakable cards">
<a class="ui card"> ... </a>
<a class="ui card"> ... </a>
<a class="ui card"> ... </a>
</div>
the cards are rendered nicely with a space between and such.
like this: refer to cards section in the link
since the cards represent some kind of view I thought of making a component out of it, so now the code is:
<div class="ui three stakable cards">
<my-card-component></my-card-component>
<my-card-component></my-card-component>
<my-card-component></my-card-component>
</div>
but now the style is broken, there is no space between them anymore.
Is there any nice way of fixing this ?
the first thing I thought of doing is this:
my-card-component OLD template:
<a class="ui card">
[some junk]
</a>
|||
VVV
my-card-component NEW template:
[some junk]
and instantiating like:
<my-card-component class="ui card"></my-card-component>
or like:
<a href="?" my-card-component></a>
but this is not satisfactory since I want to be able to pass in an object and the component would automatically set the [href]=obj.link.
in AngularJS 1.0 there was a replace: true property which does excatly what i need, is there a similar thing in Angular2 ?
There is no replace=true in Angular2. It is considered a bad solution and deprecated in Angular 1.x as well.
See also Why is replace deprecated in AngularJS?
Use an attribute-selector instead of a tag-selector in your component or directive.
Just change
#Directive({ ..., selector: "my-card-component"})
to
#Directive({ ..., selector: "a[my-card-component]"})
and use it like
<a my-card-component class="ui card"> ... </a>
You might also adjust the encapsulation strategy http://blog.thoughtram.io/angular/2015/06/29/shadow-dom-strategies-in-angular2.html but I think the default emulated should be fine in your case.
Solved it using #GünterZöchbauer Answer together with #HostBinding('href')
so now the code is:
template:
---------
[some junk]
component:
----------
#Component({
selector: 'a[my-card-component].ui.card',
templateUrl: 'urlOfSomeJunk.html',
directives: []
})
export class ProblemCardComponent {
#Input()
obj: MyObject;
#HostBinding('attr.href') get link { return this.obj.link; }
}
instantiating:
--------------
<a class="ui card" my-card-component [obj]="someBindingHere"></a>
that way the href is automatically bound to obj.link and I can rest in piece.

Knockout.js and large dataset makes dropdown list slow also

Does anyone know why the performance on this page is slow when it comes to the dropdown list on the - ALL - option? I must be doing something wrong with knockout.js for this to happen. For the smaller list of games it opens up quickly.
Tournament Schedule
Javascript
(function (app, $, undefined) {
app.viewModel = app.viewModel || {};
function Schedule() {
var self = this;
self.loaded = ko.observable(false);
self.divisionId = ko.observable();
self.games = ko.observableArray(null);
self.search = function(url) {
app.call({
type: 'POST',
data: { divisionId: self.divisionId() },
url: url,
success: function (result) {
self.games([]);
self.games.push.apply(self.games, result);
self.loaded(true);
}
});
};
self.init = function (options) {
app.applyBindings();
};
};
app.viewModel.schedule = new Schedule();
} (window.app = window.app || {}, jQuery));
Template
<div class="games hidden" data-bind="if: schedule.games(), css: { 'hidden': !schedule.games() }">
<div data-bind="if: schedule.games().length > 0">
<div data-bind="foreach: schedule.games">
<h2><span data-bind="html: Name"></span></h2>
<hr />
<div class="games row" data-bind="foreach: Games">
<div class="span4">
<div class="game game-box new-game-box">
<div class="datetime-header clearfix new-game-box">
<span class="time"><span data-bind="html: DateFormatted"></span> - <span data-bind="html: TimeFormatted"></span></span>,
<span class="gym" data-bind="text: Venue"></span>
</div>
<div class="team-game clearfix new-game-box" data-bind="css: { winner: AwayTeamIsWinner }">
<span class="team">
<a target="_blank" href="#" data-bind="html: AwayTeamName, attr: { href: AwayTeamLink }"></a>
</span> <span class="score" data-bind="html: AwayTeamScoreDisplay"></span>
</div>
<div class="team-game clearfix new-game-box" data-bind="css: { winner: HomeTeamIsWinner }">
<span class="team">
</span> <span class="score" data-bind="html: HomeTeamScoreDisplay"></span>
</div>
<div class="buttons clearfix">
<span class="division" data-bind="html: 'Division ' + DivisionName"></span>,
<span data-bind="text: GameTypeName"></span>
<div class="btn-group">
<a rel="nofollow, noindex" title="Add to calendar" href="#" class="btn btn-mini" data-bind="attr: { href: CalendarLink }"><i class="icon-calendar"></i></a>
<a target="_blank" title="Gym Details" href="#" class="btn btn-mini" data-bind="attr: { href: GymLink }"><i class="icon-map-marker"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="hidden" data-bind="if: (schedule.games() && schedule.games().length == 0), css: { 'hidden': !schedule.games() }">
No games found for this event.
Scores will be available here the day before the event however the schedule might already be posted under documents.
</div>
<script type="text/javascript">
app.viewModel.schedule.init({});
</script>
I downloaded your HTML and CSS and did some testing. I was able to fix the problem by removing the following CSS:
.ui-widget :active {
outline: none
}
To test this on the current page, execute document.styleSheets[0].deleteRule(23) in the console.
Some more testing showed that the drop-down is only slow in Chrome (30). Firefox (23) and IE (10) don't have the problem.
You may suffer from performance problems when manipulating large or rich (containing complex objects) observable arrays. Any time you perform any operation on such array, all the subscribers get notified.
Imagine you are inserting 100 items into an observable array. More often than not, you don’t need each subscriber to recalculate it’s dependencies 100 items, and UI to be reacting 100 times. Instead, once should just fine.
To do this, you can always modify the underlying array instead of the observableArray directly, since observableArray concept is just a function wrapper around the traditional JS array. After you are done with the array manipulation, you can then notify all the subscribers that the array has changed its state with .valueHasMutaded()
. See the simple example:
success: function (result) {
ko.utils.arrayPushAll(self.games, result);
self.games.valueHasMutated();
....
cheers
There are too many dom element at the page, it will be hard to select element for jquery.
If you need to handle big data bound after ajax, you'd better add a new thread to do it. in ajax success function:
setTimeout(function(){
// your code
}, 100);
for No.1, why not add a pager? Long long scroll bar is very terrible.

Can Meteor handle nested views?

I'm learning meteor, and finding all kinds of difficulties dealing with nested subviews. Since the application I want to write is full of them... that looks like a difficulty. I found this on github, as a readme for a Meteor project to try and deal with this problem.
"I've been playing around with Meteor for a couple weeks. The ease of setup and the powerful reactivity makes this something I want to stick with. I was however frustrated by the difficulty of programmatically configuring, instantiating, destroying and nesting subviews."
Is this an issue that can be handled in Meteor (without adding a lot of complicated work arounds) or should I look for a different platform ?
I love nesting templates. I get reliable results. I now program off a library of both templates and helper functions (usually for form elements) that compose html for me. HTML is a byproduct, and the files we call .html are really a javascript DSL.
There are many S.O. issues raised about insertions into sorted lists giving people problems. I haven't had time to look.
My rule of thumb: Meteor is (well) designed from the beginning to do this easily and reliably.
So far the harder thing to solve was when I added an accordion from foundation, and a refresh of the document led to its initial state (being all closed, or one open). I had to put code in that saved the current section, and code to re-assert that in the rendered callback for the template that used it.
Why not write a prototype of the nesting with just a field or two in places, and find what bothers you?
Here is a sample chain. You see all the nested templates. This template itself is running within multiple.
First template: called 'layout', suggested by iron router. Has basic page and menu. Main body is a yield, set by router. On a sample page, a route calls template 'availability'
<template name='availability'>
{{#each myAgents}}
<form class="custom" id="Agent_{{_id}}" action="">
<div id='availability' class="section-container accordion" data-section="accordion">
<section id="services">
<p class="title" data-section-title><a href="#">
Your Info
</a></p>
<div class="content" data-section-content>
{{>services}}
</div>
</section>
<section id="skills">
<p class="title" data-section-title><a href="#">
Skills
</a></p>
<div class="content" data-section-content>
{{>skills}}
</div>
</section>
<section id="sureties">
<p class="title" data-section-title><a href="#">
Sureties
</a></p>
<div class="content" data-section-content>
{{>sureties}}
</div>
</section>
<section id="time">
<p class="title" data-section-title><a href="#">
Time Available
</a></p>
<div class="content" data-section-content>
{{>time}}
</div>
</section>
<section id="schedule1">
<p class="title" data-section-title><a href="#">
Schedule 1
</a></p>
<div class="content" data-section-content>
{{>schedule}}
</div>
</section>
<section id="schedule2">
<p class="title" data-section-title><a href="#">
Schedule 2
</a></p>
<div class="content" data-section-content>
{{>schedule}}
</div>
</section>
<section id="distance">
<p class="title" data-section-title><a href="#">
Distance
</a></p>
<div class="content" data-section-content>
{{>distance}}
</div>
</section>
</div>
</form>
{{/each}}
</template>
sample further nest:
<template name='services'>
{{label_text fname='name' title='Agent Name' placeholder='Formal Name' collection='agent' passthrough='autofocus=autofocus ' }}
{{label_text fname='agentInCharge' title='Agent In Charge' placeholder='Owner' collection='agent' }}
{{label_text fname='phone' title='Phone Number(s)' placeholder='Include Area Code'collection='agent' }}
{{>gps }}
<h4>Not shared:</h4>
{{label_text fname='email' title='Email:' placeholder='you remain anonymous' collection='agent' }}
</template>
and label_text is a helper, learned from the https://github.com/mcrider/azimuth project:
generateField = (options) ->
options.hash.uniqueId = options.hash.fieldName + "_" + Math.random().toString(36).substring(7) if options.hash.template is "wysiwyg"
options.hash.id = options.hash.id or #_id
options.hash.value = options.hash.value or this[options.hash.fname]
# allow for simple params as default
options.hash.title = options.hash.title or options.hash.fname
options.hash.template = options.hash.template or "label_text"
options.hash.placeholder = options.hash.placeholder or options.hash.title
# compatible with old
options.hash.fieldName = options.hash.fieldname or options.hash.fname
options.hash.label = options.hash.label or options.hash.title
# FIXME: Return error if type not valid template
new Handlebars.SafeString(Template[options.hash.template](options.hash))
Handlebars.registerHelper "label_text", (options) ->
options.hash.collection = options.hash.collection or 'generic'
generateField.call this, options
I am fairly new to Meteor, but I found out really soon that I wanted nested views (aka dynamic includes or sub-templates). I'm not sure whether this is what you mean, but here is my solution.
I created the following handlebars helper, that can be used to create sub-templates:
Handlebars.registerHelper('subTemplate', function(container, property, context, options) {
if (container && container.hasOwnProperty(property)) {
var subTemplate = container[property];
if (typeof subTemplate === 'function') {
return new Handlebars.SafeString(subTemplate(context || this));
}
else if (typeof subTemplate === 'string') {
return new Handlebars.SafeString(Template[subTemplate](context || this));
}
}
});
It can be used inside something I call a generic template. For example to create a list:
<template name="item_list">
<ul class="items-list">
{{#each items}}
<li class="listview-item">
{{subTemplate .. 'listItem' this}}
</li>
{{/each}}
</ul>
</template>
Now invoking this generic template requires that a 'listItem' property is present within its context. This can be either a string with the name of the sub-template, or the inline definition of a sub-template. The example below shows both options:
<template name="my_list">
{{! First option, referring to the sub-template by name:}}
<div>
{{#with listData listItem="my_list_item"}}
{{> item_list}}
{{/with}}
</div>
{{! Second option, inlining the sub-template:}}
<div>
{{#with listData}}
{{#assignPartial 'listItem'}}
<span>{{name}}</span>
{{/assignPartial}}
{{> item_list}}
{{/with}}
</div>
</template>
<template name="my_list_item">
<span>{{name}}</span>
</template>
Template.my_list.listData = function() {
return {
items: collections.people.find()
};
};
The second option requires an extra handlebars helper.
Handlebars.registerHelper('assignPartial', function(prop, options) {
this[prop] = options.fn;
return '';
});
I made more of these kinds of useful helpers, at some point I will probably share them on GitHub.

Multiple 'foreach' loops in one knockout JS

I'll start by saying that I am working within the context of DotNetNuke7, which is essentially ASP.net based framework, and that i am fairly new to KO.
I am trying to have one ko viewmodel and have two foreach loops in it. Each loop renders an array which is part of the view model definition like so:
//We build two arrays: one for the users that are in the group
//and one for the users that are not in the group
var nonGroupMembers = $.map(initialData.NonGroupUsers, function (item) { return new Member(item); });
var groupMembers = $.map(initialData.GroupUsers, function (item) { return new Member(item); });
//The members we start with before we added new members
self.SearchTerm = ko.observable('');
self.CircleMembers = ko.observableArray(groupMembers);
self.NonCircleMembers = ko.observableArray(nonGroupMembers);
In the html context (or the asp user control) i placed the following code
<div id="socialDirectory" class="dnnForm dnnMemberDirectory">
<ul id="mdMemberList" class="mdMemberList dnnClear" style="display:none"
data-bind="foreach: { data: NonCircleMembers, afterRender: handleAfterRender },
css: { mdMemberListVisible : Visible }, visible: HasMembers()">
<li class="memberItem">
<div data-bind="visible: $parent.isEven($data)">
<%=MemberItemTemplate %>
</div>
<div data-bind="visible: !$parent.isEven($data)">
<%=MemberAlternateItemTemplate %>
</div>
</li>
</ul>
</div>
<div class="circleDirectory" id="circleDirectory" >
<ul id="cdMembersList" data-bind =" foreach: {data: CircleMembers, afterRender: handleAfterRender}">
<li class="memberItem">
<div class="mdMemberDetails">
<a href="" class="mdMemberImg" data-bind="attr: { title: DisplayName, href: ProfileUrl }">
<span><img data-bind="attr: { src: getProfilePicture(50,50), title: DisplayName }" /></span>
</a>
<ul class="MdMemberInfo">
<li class="mdDisplayName" >
<a href="" title="" class="mdMemberTitle"
data-bind="attr: { title: DisplayName, href: ProfileUrl },
event: { mouseover: $parent.showPopUp }">
<span data-bind="text: DisplayName"></span>
</a>
</li>
<li class="mdTitle"><p><span data-bind="text: Title"></span></p></li>
<li class="mdLocation"><p><span data-bind="text: Location()"></span></p></li>
</ul>
</div>
</li>
</ul>
</div>
Each one of the DIVs which contain the foreach binding loop in them works perfectly well without the other. For instance, the bottom div (id= cdMembersList) will work fine but when I add the upper div with the binding markups it will stop working. The same thing happens vise verse.
Does anybody have a clue why it might happen? Can i not have 2 loops in one view model?
looking forward to solving this mystery.
thanks,
David
Ok, I hate to say it but the answer is very simple as always. I didn't add to my view model the Visible property for
css: { mdMemberListVisible : Visible }
When I created a new script file I simply skipped this property. A few lessons:
You can run more than one loop in one view model.
Always check that you have all the properties defined in the view model.
Also, apparently it helps creating a question on this board since it makes you think clearly about the problem and revisit your actions. I had spent 2 hours chasing this problem before i posted my question, and then it took me 15 minutes to solve it after I posted it.

Resources