How to toggle between links in ember js? - javascript

Hi, I'm new to ember js. Can someone please help me with my code? I have created this but I don't know how can I give action to each links. When I click on BreakFast, it should only show me 3 search box(BreadType, cheeseType and meatType) only and other should hide. same for Lunch and Drinks.
I also created route for menu in router.
--------------/////application.hbs
<h1>Welcome!!</h1>
{{#link-to 'menu'}}BreakFast{{/link-to}}
{{#link-to 'menu'}}Lunch{{/link-to}}
{{#link-to 'menu'}}Drinks{{/link-to}}
{{outlet}}
-------------/////menu.hbs
<div>
<p>Hello from BreakFast</p>
<label>
Bread Type:{{input value=bread}}
Cheese Type:{{input value=cheese}}
Meat Type:{{input value=meat}}
</label>
</div>
<div>
<p>Hello from Lunch</p>
<label>
Calories:{{input value=cal}}
Price:{{input value=price}}
Veg/Non-veg:
<select>
<option>V</option>
<option>N</option>
</select>
</label>
</div>
<div>
<p>Hello from Drinks</p>
<label>
Drink Name:{{input value=name}}
Price :{{input value=price}}
Ice: <select><option>Y</option>
<option>N</option>
</select>
</label>
</div>

one way to solve this is to use Ember's router to do most of the work for you.
First we define three nested routes under application, corresponding to breakfast, lunch, and drinks:
Router.map(function() {
this.route('breakfast');
this.route('lunch');
this.route('drinks');
});
Then, we just put the common parts in the application template, along with an {{outlet}} where nested templates will be rendered into:
// application.hbs
<h1>Welcome!!</h1>
{{#link-to 'breakfast'}}BreakFast{{/link-to}}
{{#link-to 'lunch'}}Lunch{{/link-to}}
{{#link-to 'drinks'}}Drinks{{/link-to}}
{{outlet}}
And finally, you put each menu type in the respective template:
// app/templates/breakfast.hbs
<div>
<p>Hello from BreakFast</p>
<label>
Bread Type:{{input value=bread}}
Cheese Type:{{input value=cheese}}
Meat Type:{{input value=meat}}
</label>
// app/templates/lunch.hbs
<div>
<p>Hello from Lunch</p>
<label>
Calories:{{input value=cal}}
Price:{{input value=price}}
Veg/Non-veg:
<select>
<option>V</option>
<option>N</option>
</select>
</label>
// app/templates.breakfast.hbs
<div>
<p>Hello from Drinks</p>
<label>
Drink Name:{{input value=name}}
Price :{{input value=price}}
Ice:
<select>
<option>Y</option>
<option>N</option>
</select>
</label>
</div>
And now you should only see the menu type you want! Without more information I can't really advise regarding models and data binding the fields to models.
You can also use the index route that Ember automatically creates for you to provide more template for when you visit / in your app.
Hope this helps.

Edit: I created twiddle to reflect what you requested. queryParam example with single template and controller
I would encourage you to go through official ember guides and play around with ember-twiddle. Here is my comment for your reply in discuss forum,
queryparams : [type], change this to queryparams : ['type'],
In menu.js controller file, you can't write code like you did, that will throw syntax error while compiling
import Ember from 'ember';
queryparams : [type],
export default Ember.Controller.extend({
if('breakfast'){
BreakFast:true
}
else if('lunch'){
Lunch:true
}
else if('drinks'){
Drinks:true
}
});
In menu.hbs, you have used lots properties in input helper but you haven't defined in corresponding controller/model.
I saw this question earlier in discussion.
There are many ways for your use case implementation, but I implemented it for complex requirement it will scale as well.
1.twiddle for dynamic segment implementation and included dynamic component rendering using component helper.
twiddle for queryaram and included dynamic component rendering using component helper..
I implemented using URL with dynamic segment and dynamic component rendering and component for each menu type. It may be overkill for your requirement but this will give you the better idea.
router.js
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
//introducing dynamic segment for men types
this.route('menu',{path: 'menus/:menu_id'});
});
export default Router;
routes/menu.js
import Ember from 'ember';
const { isEqual } = Ember;
export default Ember.Route.extend({
model(params){
//usually we will request data from server through ember-data or ajax call
var data = '';
if(params.menu_id === '1'){
data = { bread:' bread name',cheese:'cheese name',meat:'meat name '};
} else if(params.menu_id === '2'){
data = { cal:'200',price:'20'};
}
data.menu_id = params.menu_id;
return data;
}
});
controllers/menu.js
import Ember from 'ember';
const {computed} = Ember;
export default Ember.Controller.extend({
menuNameComponent: computed('model.menu_id',function(){
return this.get('model.menu_id') === '1' ? "menu-breakfast" : "menu-lunch";
})
});
application.hbs
<h1>Welcome!!</h1>
{{#link-to 'menu' '1'}}BreakFast{{/link-to}}
{{#link-to 'menu' 2}}Lunch{{/link-to}}
{{outlet}}
menu.hbs
<!-- component helper will dynamically load the specified component -->
{{component menuNameComponent}}
{{outlet}}
templates/components/menu-breakfast.hbs like this implement it for all the menu type.
<div>
<p>Hello from BreakFast</p>
<label>
Bread Type:{{input value=bread}}
Cheese Type:{{input value=cheese}}
Meat Type:{{input value=meat}}
</label>
</div>
{{yield}}
templates/components/menu-lunch.hbs
<div>
<p>Hello from Drinks</p>
<label>
Drink Name:{{input value=name}}
Price :{{input value=price}}
Ice: <select><option>Y</option>
<option>N</option>
</select>
</label>
</div>
{{yield}}

Related

Vue component stops working when wrapped into another component

I got stucked with Vue.js. I am trying to basically wrap a component(that is already inside one component) into one more. I have a dropdown with a select and I call a function on change. Everything works fine until I wrap the component in one more on top. The top level one is in blade as it's used with Laravel. Snippets:
Component with dropdown:
<template>
<div id="watchlist-item">
<select #change="changed()" class="form-control"
id="currencies" name="currencyList">
<option value="USD" selected="selected">USD</option>
<option value="EUR">EUR</option>
</select>
</div>
</template>
<script>
export default {
name: "watchlist-item.vue",
methods: {
changed() {
alert("CHANGED");
},
},
}
</script>
Wrapper:
<template>
<div id="watchlistItem">
<watchlist-item></watchlist-item>
</div>
</template>
<script>
export default {
name: "watchlist"
}
</script>
Top component:
<template>
<div id="watchlist">
<watchlist></watchlist>
</div>
</template>
<script>
export default {
name: "main-component"
}
</script>
Blade template:
#extends('layouts.layout')
<div>
{{-- <div id="maincomponent">--}}
{{-- <main-component></main-component>--}}
{{-- </div>--}}
<div id="watchlistItem">
<watchlist-item></watchlist-item>
</div>
</div>
This works fine and i get alert on change. However, when i uncomment the commented part and vice-versa (so basically wrap it one more time) vue stops aletring me. I find this behaviour pretty weird but I am just starting with Vue so maybe its just a small detail I'm missing. I don't really even know what to search for though, so any help would be greatly appreciated. Thank you.
Just make sure that you are importing child components inside it's parent correctly:
main-component > watchlist > watchlist-item
| |
has has
Well it doesnt work because you need to register it via components, but first you need to import it.
<template>
<div id="watchlistItem">
<watchlist></watchlist>
</div>
</template>
<script>
import watchlist from "path/to/watchlist";
export default {
name: "watchlist",
components: {
watchlist: watchlist
}
}

Can't re-render html in vee-validate component

Vue.js v2.6.11 / vee-validate v3.2.2
I have a button that will push new element to form.demand (data in vue app) on click event.
And if form.demand update, html in v-for should be updated.
After I wrap it in vee-validate component , it not works.
form.demand will update, but v-for won't.
I try to put same html in test-component, when form.demand update, v-for update too.
I can't figure out why...
following is my code:
HTML
<div id="content">
<test-component>
<div v-for="demand in form.demand">{{demand}}</div>
</test-component>
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in form.demand">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
</validation-provider>
</div>
javascript
Vue.component('validation-provider', VeeValidate.ValidationProvider);
Vue.component('validation-observer', VeeValidate.ValidationObserver);
Vue.component('test-component',{
template: `
<div>
<slot></slot>
</div>
`
})
var app = new Vue({
el: "#content",
data: {
form: {
demand: [],
},
},
methods: {
addDemand(){
this.form.demand.push({
name : "demand name",
count: 1
})
}
})
------------Try to use computed & Add :key----------------
It's still not work. I get same result after this change.
HTML
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in computed_demand" :key="index">
<!--.........omitted.........-->
</validation-provider>
Javascript
var app = new Vue({
el: "#content",
// .......omitted
computed:{
computed_demand() {
return this.form.demand;
}
},
})
I think I found the problem : import Vue from two different source. In HTML, I import Vue from cdn. And import vee-validate like following:
vee-validate.esm.js
import Vue from './vue.esm.browser.min.js';
/*omitted*/
validator.js
import * as VeeValidate from './vee-validate.esm.js';
export { veeValidate };
main.js
// I didn't import Vue from vue in this file
import { veeValidate as VeeValidate } from './validator.js';
Vue.component('validation-provider', VeeValidate.ValidationProvider);
HTML
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- at end of body -->
<script src="/static/javascripts/main.js" type="module"></script>
</body>
After I fix this( import vee-validate from cdn, or import Vue by ES6 module).
It works, although it still have infinite loop issue with vee-validate.
Sorry for I didn't notice that import vue from two different source.
Please provide a key in you v-for. see code below
<div v-for="(demand,index) in form.demand" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, make a computed property that will hold your form.demands array, like this one
computed: {
form_demands: function() {
return this.form.demand
}
}
then call this computed property in your v-for
<div v-for="(demand,index) in form_demands" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, use the vue forceUpdate method
import Vue from 'vue';
Vue.forceUpdate();
Then in your component, just call the method after you add demand
this.$forceUpdate();
It is recommended to provide a key with v-for whenever possible,
unless the iterated DOM content is simple, or you are intentionally
relying on the default behavior for performance gains.

vuejs component hides everything after if not put inside a div

I started using vuejs with parcel. I have a main component App.vue from which I call a subcomponent Hello.vue using <Hello/> in App's template. I have a weird bug if I don't put the <Hello/> inside a div tag, everything that comes after in html doesn't show. The code is below:
index.js
import Vue from "vue";
import App from "./App";
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
App.vue
<template>
<div id="app">
<h3>bla bla</h3>
<div><Hello/></div>
<!-- if not put inside a div, hides everything after-->
<h2>test</h2>
<p>kldsfnlkdsjfldsfds</p>
<h5>skjdnsqkfdnlkdsqf</h5>
</div>
</template>
<script>
import Hello from "./components/Hello";
export default {
name: "App",
components: {
Hello
}
};
</script>
<style>
</style>
Hello.vue
<template>
<div>
<h1>{{ message }}</h1>
<h2>Hello {{ person.firstname}} {{person.lastname}}</h2>
<label>
Firstname:
<input type="text" v-model="person.firstname">
</label>
<label>
Lastname:
<input type="text" v-model="person.lastname">
</label>
<label>
Message:
<input type="text" v-model="message">
</label>
</div>
</template>
<script>
export default {
data() {
return {
person: {
firstname: "John",
lastname: "Doe"
},
message: "Welcome !"
};
}
};
</script>
<style>
</style>
Here is a screenshot of what I get without wrapping <Hello/> with a <div></div>
And then with a div:
Thanks !
EDIT: I don't get an error in the console. I forgot to add that I tried with webpack and I don't get this bug, so It's most likely related to parcel.
Some browsers do not display elements correctly if they use <foo /> without a closing tag, instead of <foo></foo>.
If items are not rendered with the closing tag, this may be your issue.
Some vue components will generate the closing tag from your template, even though you do not have it in your source, and others will not.
When you use a SFC (Single File Component) you must have only one element inside the <template>. Then, inside that one element you can have as many other elements as you like.
Have a look at the "Example sandbox" Simple to do app in the official documentation: https://v2.vuejs.org/v2/guide/single-file-components.html#Example-Sandbox
The file ToDoList.vue is a good example in here: https://codesandbox.io/s/o29j95wx9

Passing data to other component

Form.js
import React from 'react'
export default class Form extends React.Component{
handlePatientDisease(e){
this.setState({
patientdisease: e.target.value
})
}
handlePatientPresentIllness(e){
this.setState({
patientpresentillness: e.target.value
})
}
handlePatientName(e){
let patientName = e.target.value
this.setState({ patientName })
this.props.onPatientNameChange.value
}
handleSubmit(e){
e.preventDefault();
console.log("Patient name changed to:", patientName.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<ul>
<li>
<label> Nome do Paciente </label>
<input type="text" name="patientName" id="patientName" placeholder="nome do paciente" onChange={this.handlePatientName.bind(this)} />
</li>
<li>
<label> Doença
<input type="text" name="patientdisease" placeholder="disease"/>
</label>
</li>
<li>
<label> Histórico
<input type="text" name="patientpresentillness" placeholder="historia do paciente"/>
</label>
</li>
<li>
<button type="submit"> Submit </button>
</li>
</ul>
</form>
);
}
}
So, Now I already can get the data submitted in that form and show it on console. How do I display in another component now? I want to that data as a list item in that component below:
Sidebar.js
import React from 'react'
import patientName from './Form'
export default class Sidebar extends React.Component{
handlePatientNameChange(patientName){
console.log("Patient name changed to:", patientName)
}
render () {
return(
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li>Alexandre Miranda</li>
<li>Felipe Neves</li>
<li>Andressa Lyra</li>
<li>Artur Lyra</li>
<li>Antonio Lyra</li>
<li>Ricardo Lyra</li>
// When I submit, I want to show the patientName here as a list item....
</ul>
</div>
</div>
</div>
);
}
}
You need some sort of common data store that is accessible by both components, your form and your sidebar.
The naive approach would be to create a global array that contains your list of patients. This is accessible from anywhere in your application and can be written from the form, as well as read from the sidebar. However, there are plenty of discussions why global variables may be a bad idea, so this solution is most likely not the preferred one.
A better option would be to keep the list in the state of a common ancestor component of the form and the sidebar. Say, if your app is structured like this:
<App>
<Form />
<Sidebar />
</App>
, instead of keeping it in the state of Form, transfer it to App, store it in its state and pass it down to Sidebar via its props.
Once your application becomes more complex, you should think about a more general approach to storing your application's data in a central place outside of your React components. While there are dozens of valid approaches to this problem, you could look into Flux or Redux as a starting point.
Your example doesn't show what's the connection between the Sidebar and the Form component. Are they rendered within the same parent component?
Generally speaking, you need to add callback to the Sidebar component into the Form component. Callback will be called on submit with the new value. Sidebar component will then update its state and re-render.

Render view component with parameters into named outlet ember.js

I have 2 named outlets in my application template, slider-area and pre-footer. Is there a way to pass view components that take parameters, as in the main-slider component shown in the index template, to a named outlet? So I would need to pass {{main-slider sliders=filteredSlider}} to the outlet {{outlet "slider-area"}} in the index template?
I have come from rails so forgive my thinking if this is not how ember does it. One could specify yield :slider_area in the application template and then wrap any content for this area in a content_for :slider_area block. Is a similar approach available in ember?
index.html
<script type="text/x-handlebars" data-template-name="application">
{{panasonic-topbar}}
<div id="batterywrap">
{{outlet "slider-area"}}
<div class="index_contents">
<div class="index_contents_inner">
<div class="main_area">
{{outlet}}
</div>
</div>
</div>
</div>
{{panasonic-footer}}
</script>
<script type="text/x-handlebars" data-template-name="index">
# Something like {{outlet "slider-area" render main-slider sliders="filteredSlider}} ?
{{main-slider sliders=filteredSlider}}
{{partial "social_footer"}}
</script>
app.js
App.IndexController = Ember.ObjectController.extend({
filteredSlider: function(){
return this.get('sliders').filterBy('page', 'index');
}.property('sliders.#each.page')
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
sliders: this.store.find("slider")
});
}
});
Ok, so I have a solution to this issue, rather than trying to pass a view component within a template to a specific outlet (the ruby on rails way), the key is to create a template, not a component, and render this from within the Route to the specific outlet.
When rendered from Route, the slider template has access to all of the functions in the scope of the Routes controller, so we namespace the functions/arguments universally across all controllers that will use this template and our dynamic parameters should work.
So below in the IndexRoute we define the data that we send to the controller, sliders, we also specify that we want to render normal content in the default outlet using this.render();, and then we render our shared slider template into the named outlet "slider-area". Then in our controller, we filter the models data to our specification and we name this function batterySliders across all controllers that use this feature.
app.js
App.IndexController = Ember.ObjectController.extend({
batterySliders: function(){
return this.get('sliders').filterBy('page', 'index');
}.property('sliders.#each.page')
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
sliders: this.store.find("slider"),
});
},
renderTemplate: function(){
this.render();
this.render("slider", {outlet: "slider-area"});
}
});
index.html
<script type="text/x-handlebars" data-template-name="slider">
{{panasonic-navigation tagName="div" classNames="gnavi_area"}}
<div class="slider_wrap">
<div id="slider" class="main_slider">
{{#each slider in batterySliders}}
<div class="slider_area slider0{{unbound slider.id}} {{unbound slider.background}}">
<div class="slider_inner">
<div class="inner0{{unbound slider.id}}">
<img {{bind-attr src="slider.image" alt="slider.image"}} class="nosp"/>
<img {{bind-attr src="slider.sm_image" alt="slider.sm_image"}} class="sp"/>
</div>
</div>
</div>
{{/each}}
</div>
</div>
</script>
application.html
<script type="text/x-handlebars" data-template-name="application">
{{panasonic-topbar}}
<div id="batterywrap">
<div class="content_head">
{{outlet "slider-area"}}
</div>
<div class="index_contents">
<div class="index_contents_inner">
<div class="main_area">
{{outlet}}
</div>
</div>
</div>
</div>
{{panasonic-footer}}
</script>

Categories

Resources