Polymer: How to access individual items in collection - data-binding

I am creating a polymer element which shows a dropdown for selecting different div which we call sections here. On selection of an item in the dropdown the respective section should show up. You can consider this more like a tab control where instead of a tab header we have a dropdown control. I want to bind each section with the respective json object but using index like sections[0] and sections[1] is not working
<polymer-element name="my-elem">
<template>
<div class="sections">
<select on-change="{{sectionChanged}}">
<template repeat="{{ sections }}">
<option>{{ name }}</option>
</template>
</select>
<div id="section_0" class="section-config" bind="{{ sections[0] }}">
<div>{{ sec0Prop }}</div>
Just for simplicity I am showing only one div here. This div can actually be quite complex
</div>
<div id="section_1" class="section-config" bind="{{ sections[1] }}">
<div>{{ sec1Prop }}</div>
This section view will be different than that of section 0
</div>
</div>
</div>
</template>
<script>
Polymer('my-elem', {
sections: [{ sec0Prop: 'I am section 1 property' }, { sec1Prop: 'I am section 2 property' }],
showSection: function(index) {
$(this.shadowRoot.querySelectorAll('.section-config')).hide();
$(this.shadowRoot.querySelector('#section_' + index)).show();
},
sectionChanged: function(e) {
this.showSection(e.target.selectedIndex);
}
});
</script>
</polymer-element>
Assume jquery is used.
Can you please help me with how to bind to individual items in a collection when we cannot use repeat in template as the view for each item is different?

You have id. So best way to get item: this.$['section_' + index]. Why you don't use 'core-pages'? With 'core-pages' you can do it without js.

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.

Avoid applying variable to nested Handlebars partial blocks

I have a Handlebars partial called "section", defined as following:
<div{{#if class}} class="{{class}}"{{/if}}>
<h3>{{ title }}</h3>
{{> #partial-block }}
</div>
In certain cases I need to use it as follows:
{{#> section title="title1" class="class1" }}
{{#> section title="title2" }}
<!-- content -->
{{/ section }}
{{/ section }}
The problem is that the "title2" section will also have "class1" resulting in this:
<div class="class1">
<h3>title1</h3>
<div class="class1">
<h3>title2</h3>
<!-- content -->
</div>
</div>
The question: is there a way to avoid this, without having to create a second partial e.g. "section_class1"?
Since you are setting the data parameters (ex., title="title1") for your partial directly within your template, the simplest way to prevent the nested section partial from inheriting the class of its parent would be to explicitly set an empty value for the class parameter where the nested partial is declared:
{{#> section title="title1" class="class1" }}
{{#> section title="title2" class="" }}
<!-- content -->
{{/ section }}
{{/ section }}
I have created a fiddle for reference.
A more robust - and scalable - approach might be to represent your sections in a data structure and pass that to your template. For example, you could define a root section with an array of child sections, like:
{
class: 'class1',
title: 'First Section',
sections: [
{
class: '',
title: 'Subsection One'
},
{
class: 'class1',
title: 'Subsection Two'
}
]
};
This gives you the granular control to apply a specific class to each child section.
Your template would then need to iterate over the child sections array using an #each:
{{#> section }}
{{#each sections }}
{{#> section this }}
<!-- content -->
{{/ section }}
{{/each}}
{{/ section }}
I have created a fiddle demonstrating this alternative.

Meteor - Blaze easy-search template - how to insert input field attributes into template?

So I have an easy-search template:
<template name="searchBox">
<div class="">
{{> EasySearch.Autosuggest index=PlayersIndex }}
</div>
</template>
And I'd like to make the input field look like this (have the following attributes):
<input
type="text"
placeholder="Type to add new player"
ref="textInput"
/>
I've tried adding the attributes to the argument but that doesn't seem to work:
{{> EasySearch.Autosuggest index=PlayersIndex type="text"}}
Any ideas how to achieve this?
Just add attributes property in your HTML:
{{> EasySearch.Input index=index attributes=inputAttributes}}
And in your JS, fill it with your needed data:
`Template.leaderboard.helpers({
inputAttributes: function () {
return { 'class': 'easy-search-input', 'placeholder': 'Start searching...' };
}
)}
`
I was able to find the answer by looking at this repo, so make sure to check github repos as they might contain helpful examples. ;)

Meteor Blaze : dont wait for child template rendering before rendering parent

playing with Blaze i realized the following :
if i have a parent template where i include a child template with {{> child_template }}
then Blaze will wait for child template to be rendered before rendering parent. This can be good in some cases but not all.
For e.g if my parent template contains a <h1>Welcome to my page</h1> and child a list of 10 000 items. I would like a way to display <h1> asap and wait for the 10 000 items to appear later
what i'm doing currently to manage that is the following :
Template.parent.onRendered(function(){
Blaze.render(Template.child, document.body);
});
it is working but i wonder if anyone have a better solution for this problem that seems pretty common. thanks
You could pass a custom boolean argument to the child component that's false by default, but the parent component's onRendered sets it true. And the child component should check this argument and not render anything unless it's true.
Template.parent.onCreated(function() {
this.state = new ReactiveDict();
this.state.setDefault({
"canRender": false,
});
}
Template.parent.onRendered(function() {
this.state.set("canRender", true);
}
Template.parent.helpers({
canRender() {
return Template.instance().state.get("canRender");
}
});
Pass the state to the child component:
<template name="parent">
{{>child canRender=canRender}}
</template>
<template name="child">
{{#if canRender}}
<p>Millions of items go here.</p>
{{/if}}
</template>
As you say, your Child Template has a list of 10000 items. so, this means you have subscribed to some collection. You can us below code to solve your issue.
<template name="Parent">
<div>
<h1>Welcome to my page</h1>
</div>
{{#if Template.subscriptionsReady}}
{{> Child}}
{{else}}
<p>Loading Child Items...</p>
{{/if}}
</template>

VueJS component won't render on front end (possibly Drupal issue)

The site I'm currently working on is built in Drupal 7. I have one form that requires user input so I'm attempting to build it with VueJS. The form is contained all within one template file (.tpl.php) and all the content is provided in this template file or via the VueJS Javascript (nothing is coming from the CMS).
The issue I have is that the Vue components are not rendering on the front-end, but when I copy the code into a JSFiddle they do, so I'm guessing it is an issue with the interaction between VueJS and Drupal. Here is a screenshot of my markup when inspecting...
Here is the code from the .tpl.php file...
<div id="app">
<form>
<div>
<label for="year">Per Year</label>
<input type="radio" name="frequency" id="year" value="year" v-model="frequency" checked>
<label for="month">Per Month</label>
<input type="radio" name="frequency" id="month" value="month" v-model="frequency">
</div>
</form>
<ul class="plans">
<template id="plan-component">
<h2 class="plan-name">{{ name }}</h2>
<h2 class="plan-cost">{{ price }}</h2>
<h2 class="plan-tagline">{{ tagline }}</h2>
Choose this plan
</template>
<li>
<plan-component :frequency="frequency"
name="Basic"
tagline="Basic tagline"
price-yearly="Free"
price-monthly="Free"
></plan-component>
</li>
<li>
<plan-component :frequency="frequency"
name="Rec"
tagline="Rec tagline"
price-yearly="3"
price-monthly="4"
></plan-component>
</li>
<li>
<plan-component :frequency="frequency"
name="Team"
tagline="Team tagline"
price-yearly="4"
price-monthly="5"
></plan-component>
</li>
<li>
<plan-component :frequency="frequency"
name="Club"
tagline="Club tagline"
price-yearly="5"
price-monthly="6"
></plan-component>
</li>
</ul>
</div>
..and the code from my JS file...
Vue.component('plan-component', {
template: '#plan-component',
props: ['frequency', 'name', 'tagline', 'priceYearly', 'priceMonthly'],
computed: {
'price': function() {
if (this.frequency === 'year') {
return this.priceYearly;
} else {
return this.priceMonthly;
}
}
},
methods: {
makeActivePlan() {
// We dispatch an event setting this to become the active plan
this.$dispatch('set-active-plan', this);
}
}
});
new Vue({
el: '#app',
data: {
frequency: 'year',
activePlan: {name: 'no', price: 'You must select a plan!' }
},
events: {
'set-active-plan': function(plan) {
this.activePlan = plan;
}
},
});
And here is the JSFiddle which outputs the components correctly - https://jsfiddle.net/2xgrpLm6/
What browser are you using? <template> tags are not supported in IE.
Another idea is to make sure you are never using fragment components (meaning wrap everything inside your template with a div like so:
<template id="foobar">
<div>
CONTENT HERE
</div>
</template>
Lastly, have you turned on Vue debug mode? Before you instantiate your Vue instance, set Vue.config.debug = true and see if you get console errors then.
Try moving the <template id="plan-component">...</template> code outside of the Vue instance. I.e., such that it is not contained within <div id="app">...</div>.
This has solved a similar problem for me in the past, though I'm not sure if it applies here.
For anyone having a similar issue, the solution was simple. After Jeff suggested turning on Vue debug mode (and downloading the Dev version of Vue JS instead of minified - https://vuejs.org/guide/installation.html) the console gave the error [Vue warn]: Cannot find element: #app.
The issue was that Drupal was loading my scripts in the <head>, before <div id="app"> was loaded in the DOM. As such #app couldn't be found. After outputting the scripts before the closing <body> tag all was sorted. See here for more information [Vue warn]: Cannot find element

Resources