Ember component bind-attr className doesn't change - javascript

I'm trying to do something very simple here and I couldn't make it work so far. It's probably me doing silly things.
You click in the circle to turn on and off a post. You can see that the alert message is coming right, the parameter is being updated, however I can't make the className change.
Here's the JS Bin: http://jsbin.com/tusimozo/1/edit
<script type="text/x-handlebars" data-template-name="index">
{{ group-list posts=model }}
</script>
<script type="text/x-handlebars" id="components/group-list">
<div class="row">
<table width="100%">
<thead>
<tr>
<th width="90">Status</th>
<th align="left">Categories</th>
</tr>
</thead>
<tbody>
{{# each item in posts}}
{{list-item published=item.published title=item.title pubDate=item.pub_date}}
{{/each}}
</tbody>
</table>
</div>
</script>
<script type="text/x-handlebars" id="components/list-item">
<tr>
<td>
<a href="#" {{bind-attr class=":post-status published:on:off"}} {{action "publish"}}></a>
</td>
<td align="left">{{title}}</td>
</tr>
</script>
app.js
App = Ember.Application.create();
posts = [{
title: "Items",
published: true
}, {
title: "Boards",
published: false
}];
App.IndexRoute = Ember.Route.extend({
model: function() {
return posts;
}
});
App.ListItemComponent = Ember.Component.extend({
actions: {
publish: function() {
var publishStatus = this.get('published') ? false : true;
this.set('published', publishStatus);
alert(this.get('published') ? 'publish' : 'unpublish');
}
}
});
What am I missing here?
Cheers!

So basically you should use classbindings on the component.
App.ListItemComponent = Ember.Component.extend({
tagName:'tr',
classNameBindings: ['isPublished:on:off'],
isPublished: function() {
return this.get('published');
}.property('published'),
actions: {
publish: function() {
this.toggleProperty('published');
}
}
});
JSBIN:
http://jsbin.com/mixeniheze/1/edit

You can add more than one class by using classNames as well.
classNames: ['class-name-1'],
classNameBindings: ['isSomethingTrue:class-name-2:class-name-3']
Further references here: https://guides.emberjs.com/v1.10.0/components/customizing-a-components-element/

Related

Vue.js pass a callback as prop to child component and execute on parent when it is clicked

I am trying to create a dropdown component, in which it receives various data and the list items are built dynamically, but I am having difficulty detecting the click on any item in the list
parent
<Dropdown
:items="[
{
text: 'Edit',
icon: 'fal fa-edit',
click: editFunction(id)
},
{
text: 'Delete',
icon: 'fal fa-trash-alt',
click: deleteFunction(id)
}
]"
/>
child Dropdown.vue
<template>
<div class="dropdown">
<a class="trigger">
<i class="far fa-ellipsis-h"></i>
</a>
<ul class="items">
<li v-for="(item, index) in items" :key="index" class="item">
<a>
<i :class="item.icon"></i>
{{ item.text }}
</a>
</li>
</ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props: {
items: {
type: Array,
default: () => []
}
}
})
</script>
currently in this way, as soon as the parent component is created, the editFunction(id) and deleteFunction(id) methods are executed.
I know it's possible because vuetifyjs it does that way, but I tried to inspect the source code but I got nowhere.
What you want to achieve can be done by
parent.vue
<child-componet :parent-method="thisIsTheMethod" />
...
methods: {
thisIsTheMethod()
{
//here it does something
}
}
note that the method passed inside the prop does not have the parenthesis () because you are passing a reference to it.
To use it inside the child component add the ()
#click="parentMethod()"
To go back to your example, change this:
<Dropdown
:items="[
{
text: 'Edit',
icon: 'fal fa-edit',
click: editFunction(id)
},
{
text: 'Delete',
icon: 'fal fa-trash-alt',
click: deleteFunction(id)
}
]"
/>
to
<Dropdown
:items="[
{
text: 'Edit',
icon: 'fal fa-edit',
click: () => editFunction(10)
},
{
text: 'Delete',
icon: 'fal fa-trash-alt',
click: () => deleteFunction(20)
}
]"
/>
and leave your editFunction(id) method declaration as is. The argument will be automatically injected.
Despite this solution would work, the best way to achieve this communication between parent and child would be to emit the value in the child and then listen for it
there have betters ways to do it, but about your idea should be as below:
Vue.component('dropdown', {
template: '#dropdown-template',
props: {
items: {
type: Array,
default: () => []
}
}
})
new Vue({
el: '#app',
data() {
return {
users: [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
]
}
},
methods: {
editFunction(id) {
console.warn('edit item ' + id)
},
deleteFunction(id) {
console.warn('delete item ' + id)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>actions</tr>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>
<dropdown
:items="[
{
text: 'Edit',
icon: 'fal fa-edit',
event: 'edit-function'
},
{
text: 'Delete',
icon: 'fal fa-trash-alt',
event: 'delete-function'
}
]"
#edit-function="editFunction(user.id)"
#delete-function="deleteFunction(user.id)"
/>
</td>
</tr>
</tbody>
</table>
</div>
<script type="text/x-template" id="dropdown-template">
<div class="dropdown">
<a class="trigger">
<i class="far fa-ellipsis-h"></i>
</a>
<ul class="items">
<li v-for="(item, index) in items" :key="index" class="item">
<a #click="$emit(item.event)">
<i :class="item.icon"></i>
{{ item.text }}
</a>
</li>
</ul>
</div>
</script>

Toggle Class for an item Vue.js

So I have a list of data brought in using vue resource from an api. I'm trying to integrate this list with bootstrap accordion.
So:
I have set this :
data: {
taller: false
}
and
<div class="panel panel-default"
v-repeat="faq: faqs | filterBy searchText"
v-transition="staggered"
stagger="200"
v-on="click: toggleHeight"
v-class="active: taller">
So on click i'll call toggleHeight and pass through the faq instance :
toggleHeight: function(faq) {
this.taller = true;
}
This function sets to taller to true however it sets it to true for all faq items, not just the one i've passed to toggle height.
How can I only return taller: true for clicked faq item?
Thanks
<div id="demo">
<div class="input-group">
<input class="form-control" v-model="searchText">
</div>
<script id="item-template" type="x-template">
<div
class="stag"
v-on="click: toggleHeight()"
v-class="active: taller"
>
<h3>{{ item.question }}</h3>
<h4>{{ item.answer }}</h4>
</div>
</script>
<question
v-repeat="item: items | filterBy searchText"
v-transition="staggered"
stagger="200">
</question>
</div>
</div>
and the js:
Vue.component('question', {
template: document.querySelector('#item-template'),
data: function() {
return {
taller: false
}
},
methods: {
toggleHeight: function() {
this.taller = ! this.taller
}
}
});
new Vue({
el: '#demo',
ready: function() {
this.fetchItems();
},
methods: {
fetchItems: function() {
this.$http.get('/dev/frequently-asked-questions/api/index', function(items) {
this.$set('items', items);
});
}
}
});
Had to make use of components to target each item more directly.
First of all, you only have one variable taller. Not one per faq. So you'd have to change it a bit to something like this:
new Vue({
el: '#faq-list',
data: {
faqs: [{id:'foo',taller:false},{id:'bar',taller:true}]
},
methods: {
toggleHeight: function (faq, ev) {
faq.taller = false;
ev.target.classList.add('active');
}
}
});
And your HTML to something like this:
<div id="faq-list">
<div class="panel panel-default"
v-repeat="faq: faqs"
v-on="click: toggleHeight (faq, $event)"
v-class="active: taller">
{{ faq.id }}</div>
</div>
And for fun, I added CSS to see things working:
.active {
color: red;
}
Here's the JSfiddle, for your reference and to mess around with it. Click on one of the items in the list and it turns red.

Ember nested route and models not rendering (at all)

I can get the "Help Topics" title to render, but none of the FIXTURES that I've defined. Nothing in the {{#each model}} will render. This is my first time working with Ember, so anything (literally anything) will help as I'm not receiving any error messages in the browser debugger.
App.js
App.Router.map(function () {
this.resource('home', { path: "/Index" });
this.resource('agents', { path: "/Agents" });
this.resource('topics', function() {
this.resource('topic', {path: '/topic/:topic_id'})
});
this.resource('contacts', { path: "/Contacts" });
});
App.TopicRoute = Ember.Route.extend({
model: function () {
return App.Topic.find();
}
})
App.Topic = DS.Model.extend({
title: DS.attr('string'),
info: DS.attr('string')
});
App.Topic.FIXTURES = [{
id: 1,
title: "Periscope",
info: "Periscope is a read-only application pulling information from D3."
}, {
id: 2,
title: "Second post",
info: "ASP.NET MVC 4 is a framework for building scalable, standards-based web applications using well-established design patterns and the power of ASP.NET and the .NET Framework."
}, {
id: 3,
title: "Ember.js",
info: "Ember.js is designed to help developers build ambitiously large web applications that are competitive with native apps."
}];
View
<script type="text/x-handlebars">
<div class="navbar">
<div class="navbar-inner">
<ul id="menu">
<li>{{#linkTo 'home'}}Home{{/linkTo}}</li>
<li>{{#linkTo 'agents'}}Agents{{/linkTo}}</li>
<li>{{#linkTo 'topics'}}About{{/linkTo}}</li>
<li>{{#linkTo 'contacts'}}Contact{{/linkTo}}</li>
</ul>
</div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" id="topics">
<div class="container-fluid">
<div class="row-fluid">
<div class="span3">
<table class='table'>
<thead>
<tr><th>Help Topics</th></tr>
</thead>
{{#each model}}
<tr><td>
{{#linkTo 'topic' this}}{{title}} {{/linkTo}}
</td></tr>
{{/each}}
</table>
</div>
<div class="span9">
{{outlet}}
</div>
</div>
</div>
</script>
<script type="text/x-handlebars" id="topic">
<h1>{{title}}</h1>
<div class="intro">
{{info}}
</div>
</script>
Try to change your route like this:
App.TopicsRoute = Ember.Route.extend({
model: function () {
return App.Topic.find();
});
Hope it helps.

Emberjs - real time changes not working?

I've followed the tutorial of one of the creators of Emberjs: http://www.youtube.com/watch?v=Ga99hMi7wfY
The goal of tutorial is to present basics of emberjs on an example, where there's a list of posts and if you edit one, the content gets mapped to the corresponding textarea or textfield. Then, the changes are visible in real time.
This, however, doesn't work. And I've been following the official tutorial (obviously, emberjs has changed a lot since this yt video).
Anyways, what am I missing?
Here's my code (HTML):
<script type="text/x-handlebars">
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">Bloggr</a>
<ul class="nav">
<li>{{#linkTo 'posts'}}Posts{{/linkTo}}</li>
<li>{{#linkTo 'about'}}About{{/linkTo}}</li>
</ul>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="about">
<div class="about">
random text about
</div>
</script>
<script type="text/x-handlebars" id="posts">
{{#if isEditing}}
{{partial 'post/edit'}}
<button {{action 'save'}}>Done</button>
{{else}}
<button {{action 'edit'}}>Edit</button>
{{/if}}
<div class="about">
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Author</th>
<th>Date</th>
<th>Utilities</th>
</tr>
{{#each model}}
<tr>
<td>{{id}}</td>
<td>{{title}}</td>
<td>{{author}}</td>
<td>{{publishedAt}}</td>
<td>{{#linkTo "post" this}}Details{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="post/_edit">
<p>{{view Ember.TextArea valueBinding="title" placeholder="test.."}}</p>
<p>{{view Ember.TextField valueBinding="author"}}</p>
</script>
<script type="text/x-handlebars" id="post">
<h1>{{title}}</h1>
<h2>by {{author}}</h2>
<small>{{date publishedAt}}</small>
</script>
This is the emberjs part:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function(){
this.resource("posts", function(){
this.resource("post", { path: ':post_id'});
});
this.resource("about");
});
App.PostsRoute = Ember.Route.extend({
model: function(){
return App.Post.find();
}
});
App.PostsController = Ember.ObjectController.extend({
isEditing: false,
edit: function(){
this.set("isEditing", true);
},
save: function() {
this.set("isEditing", false);
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
publishedAt: DS.attr('date')
});
App.Post.FIXTURES = [
{
id:1,
title: "This is my title",
author: "John Doe",
publishedAt: new Date('12-27-2012')
},
{
id:2,
title: "This is another title",
author: "Jane Doe",
publishedAt: new Date('02-03-2013')
}
];
Ember.Handlebars.registerBoundHelper('date', function(date){
return date.getFullYear();
});
Name of controller should be PostController instead of PostsController. They are responsible for handling different routes.
#wedens is right you have a wrongly named controller for the single post, you should also check out this github repo which contains the (correct) source code for that example app build in the video.

How to display child object view from parent array view

I have the below which displays a list of products. When I click on a product I would like to see just the product information.
The only way I've managed to get the product info to appear is to use the {{outlet}} or {{render}} on the parent products template which is not what I want.
var App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
});
App.ProductsController = Ember.ArrayController.extend({
sortProperties: ['id']
});
App.Router.map(function () {
this.resource('products', function () {
this.resource('product', { path: ':product_id' });
});
});
App.ProductsRoute = Ember.Route.extend({
model: function () {
return App.Products.find();
}
});
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
App.Products = DS.Model.extend({
title: DS.attr('string'),
artist: DS.attr('string'),
image: DS.attr('string'),
price: DS.attr('number'),
url: DS.attr('string')
});
App.Products.FIXTURES = [
{
id: 1,
title: 'The Door',
artist: 'Religious Knives',
image: 'http://ecx.images-amazon.com/images/I/51og8BkN8jL._SS250_.jpg',
large_image: 'http://ecx.images-amazon.com/images/I/51og8BkN8jL._SS500_.jpg',
price: 9.98,
url: 'http://www.amazon.com/Door-Religious-Knives/dp/B001FGW0UQ/?tag=quirkey-20'
},
//etc etc
];
-------------MARKUP------------
<script type="text/x-handlebars" data-template-name="products">
<span>{{ controller.model.length }} item(s) in stock</span>
<div>
{{#each product in controller.model}}
<div class="item">
<div class="item-image">
{{#linkTo "product" product}}
<img {{bindAttr src="product.image" alt="product.title"}}>
{{/linkTo}}
</div>
<div class="item-artist">{{product.artist}}</div>
<div class="item-title">{{product.title}}</div>
<div class="item-price">${{product.price}}</div>
</div>
</div>
{{/each}}
{{render product}}
</script>
<script type="text/x-handlebars" data-template-name="product">
<div class="item-detail">
<div class="item-image">
<img {{bindAttr src="large_image" alt="title"}} />
</div>
<div class="item-info">
<div class="item-artist">{{artist}}</div>
<div class="item-title">{{title}}</div>
<div class="item-price">${{price}}</div>
<div class="item-form">
<p>
<label>Quantity:</label>
<input type="text" size="2" data-bind="value: quantity" />
</p>
<p>
<button data-bind="click: $parent.addItem">Add to Cart</button>
</p>
</div>
<div class="item-link"><a {{bindAttr href="url"}}">Buy this item on Amazon</a></div>
<div class="back-link">« Back to Items</div>
</div>
</div>
</script>
UPDATE: This kind of works:
The issue is when going from a product back to products the products are no longer listed
App.ProductsRoute = Ember.Route.extend({
model: function () {
return App.Products.find();
}
});
App.ProductRoute = Ember.Route.extend({
model: function (params) {
return App.Products.find(params.product_id);
},
renderTemplate: function () {
this.render('product', { // the template to render
into: 'application', // the template to render into
outlet: 'main', // the name of the outlet in that template
});
}
});
You could change change your router mappings to something like this:
App.Router.map(function () {
this.route('products');
this.route('product', { path: 'product/:product_id' });
});
Also consider using an index route which transfers you to a specific location when the page loads:
App.IndexRoute = Em.Route.extend({
redirect: function() {
this.transitionTo('products');
}
});
Fiddle: http://jsfiddle.net/schawaska/d2FtF/
Run: http://jsfiddle.net/schawaska/d2FtF/show

Categories