How to add conditional operator with string in react - list

I'm making a list using React-JS. I'm struggling to sort the data using conditional operator.
There is a list of friends (with inputs for name and gender).
Data should be sorted according to gender.
When you click the 'male' button, only the item with 'male' value should show and the value with 'female' should be hidden.
When you click the 'female' button, only the item with 'female' value should show and the value with 'male' should be hidden.
I added the conditional operator but it didn't work. The conditional operator with 'string' will work in React?
style={{ display : this.state.male && friend.gender == "male" ? "block" : "none" }}
List.js
class FriendList extends Component {
constructor(props, context) {
super(props, context);
this.state = {
male: false,
female: false
}
}
maleButton(e) {
this.setState({
male: !this.state.male
});
}
femaleButton(e) {
this.setState({
female: !this.state.female
});
}
render() {
return (
<div>
<button onClick={this.maleButton.bind(this)}>Male</button>
<button onClick={this.femaleButton.bind(this)}>Female</button>
<ul className={styles.friendList}>
{
this.props.friends.map((friend, index) => {
return (
<FriendListItem
key={index}
id={index}
name={friend.name}
starred={friend.starred}
gender={friend.gender}
style={{ display : this.state.male && friend.gender == "male" ? "block" : "none" }}
{...this.props.actions} />
);
})
}
</ul>
</div>
);
}
}
FriendList.propTypes = {
friends: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
export default FriendList;
The data will be like this:
const initialState = {
friendsById: [{
name: 'Theodore Roosevelt',
starred: true,
gender: 'male'
}, {
name: 'Abraham Lincoln',
starred: false,
gender: 'male'
}, {
name: 'George Washington',
starred: false,
gender: 'male'
}, {
name: 'Hillary Clinton',
starred: false,
gender: 'female'
}]
};

Maybe change your approach a little bit and filter results first:
this.props.friends
.filter(friend =>
this.state.male && friend.gender === 'male' ||
this.state.female && friend.gender === 'female'
)
.map((friend, index) => ...)
Unless you need to display both male and female genders at the same time, another suggestion I give you is to remove duplication per se (with both boolean values in state) and store a single gender value as string for example. With that, filtering approach becomes even easier to understand:
this.props.friends
.filter(friend => friend.gender === this.state.gender)
.map((friend, index) => ...)

You can use .filter for that purpose instead of .map.For that you can simply do as:
{
this.props.friends.filter((friend, index) => {
if((this.state.male && friend.male) || (this.state.female && friend.female)) {
return (
<FriendListItem
key={index}
id={index}
name={friend.name}
starred={friend.starred}
gender={friend.gender}
style={{ display : "block" }}
{...this.props.actions} />
);
}
})
}

Related

How can I toggle specific list items based on catergory?

I have two lists. One contains categories, which are used to group items from a secondary list.
What I am trying to do is to create a toggle, where you click on the name of a category, and then the items for that category will appear. I managed to create the toggle, but the issue is that when I click on a category, it toggles ALL of the other categories as well.
Is there a way of telling Vue to only toggle the items that belongs to the category that is clicked?
Here is my code.
<template>
<b-container>
<b-card class="my-4">
<b-card-title>
<i :class="icons.LIST"></i>
{{ $t('OverviewTitle) }}
</b-card-title>
<loading-message v-if="!quickListCategories"/>
<not-found v-else-if="!filteredQuickListCategories.length"/>
<b-card-text v-else v-for="(category, index) in filteredQuickListCategories"
:key="category.name">
<h5 v-b-toggle.collapse :class="{'mt-4': index}">
<i v-for="(icon, index) in category.icons" :key="index" class="fa-fw" :class="icon"></i>
{{ $t(category.text) }}
</h5>
<b-collapse id="collapse">
<b-list-group>
<b-list-group-item v-for="list in category.lists" :to="list.to">
<i class="fa-fw" :class="list.icon"/> {{ $t(list.text) }}
<div class="mx-4 d-flex w-100 justify-content-between">
<small>{{ $t(list.subText) }}</small>
</div>
</b-list-group-item>
</b-list-group>
</b-collapse>
</b-card-text>
</b-card>
</b-container>
</template>
<script>
import icons from '#/models/entity/icon';
import CommonInput from '#/components/common/CommonInput';
import LoadingMessage from '#/components/LoadingMessage';
import NotFound from '#/views/NotFound';
import {getQuickListCategories} from '#/models/company/quick-lists';
export default {
components: {
CommonInput,
NotFound,
LoadingMessage
},
data() {
return {
icons,
quickListCategories: null
};
},
created() {
this.$title(this.$t('OverviewTitle));
getQuickListCategories()
.then(quickListCategories => this.quickListCategories = quickListCategories)
.catch(() => {
this.quickListCategories = [];
this.$bvModalExt.msgBoxError();
});
}
};
</script>
const quickLists = [
{
icon: icons.LIST,
to: '/list/link1',
area: 'area A',
text: 'Text 1',
categoryName: 'CATEGORY 1',
},
{
icon: icons.LIST,
to: '/list/link2',
area: 'area B',
text: 'Text 2',
categoryName: 'CATEGORY 2',
},
{
icon: icons.LIST,
to: '/list/link3',
area: 'area C',
text: 'Text 3',
categoryName: 'CATEGORY 3',
}
];
const quickListCategories = [
{
icons: ['fas fa-hand-point-right', 'fas fa-store-alt'],
text: 'HEADER 1',
name: 'CATEGORY 1'
},
{
icons: ['fas fa-hand-point-right', 'fas fa-store-alt'],
text: 'HEADER 2',
name: 'CATEGORY 2'
},
{
icons: ['fas fa-hand-point-right', 'fas fa-store-alt'],
text: 'HEADER 3',
name: 'CATEGORY 3'
}
];
export function getQuickListCategories() {
return userService.getCurrentUserAreaPermissionMap().then(({data: userAreaPermissionMap}) =>
quickListCategories
.map(category => ({
...category,
lists: quickLists
.filter(list => list.categoryName === category.name &&
(!list.area || userAreaPermissionMap[list.area] && userAreaPermissionMap[list.area] !== 'NOT_ALLOWED'))
}))
.filter(category => category.lists.length));
console.log(this.lists)
}

Extend query function to support multiple search criteria in CouchDB/PouchDB

I'm trying to get a count of docs having level: 2, completed: true. However, I'm having trouble wrapping my head around introducing another criteria in the query function. Currently, as evident from the code; it is just printing out docs having completed: true. How can I extend this query to support another query parameter like level too?
[{
_id: 1,
name: 'Test_01',
level: 1,
completed: false
},
{
_id: 2,
name: 'Test_02',
level: 2,
completed: true
},
{
_id: 3,
name: 'Test_01',
level: 3,
completed: false
}]
const myMapReduceFun = {
map: (doc) => {
emit(doc.completed);
}.toString(),
reduce: '_count'
};
db.query(myMapReduceFun, {
key: true, reduce: true
})
.then((result) => {
console.log(result)
})
This is easily done with map/reduce. One strategy is to use complex keys, the other using clever demarcations in a string.
I prefer complex keys as it does not require having to assemble the key or other string based monkey business.
Consider the design document in the demo:
{
_id: "_design/my_index",
views: {
completed_str: {
map: `function (doc) {
emit(doc.completed + '/' + doc.level + '/')
}`,
},
completed_complex: {
map: `function (doc) {
emit([doc.completed,doc.level])
}`,
},
},
}
completed_str uses concatenation and a '/' to create two fields for completed and level
completed_complex uses an array to create a complex key
In the snippet below I've included an example of both approaches. The key (no pun intended) is to emit the 'completed' field first, then the 'level' field.
When toying with the queries, do note the difference in the value Key field returned by the view.
const gel = id => document.getElementById(id);
const g_view_result = 'view_result';
function getQuery() {
let view = gel('view').value;
let completed = gel('completed').value === 'true';
let level = parseInt(gel('level').value, 10);
if (view === 'complex') {
// use complex key view
return {
view: "my_index/completed_complex",
params: {
reduce: false,
include_docs: false,
start_key: [completed, level],
end_key: [completed, level],
}
}
}
// use simple string view
return {
view: "my_index/completed_str",
params: {
reduce: false,
include_docs: false,
start_key: [completed, level, ''].join('/'),
end_key: [completed, level, ''].join('/'),
}
}
}
async function query() {
try {
let html = [];
const view_result = gel(g_view_result);
view_result.innerText = '';
let query = getQuery();
let docs = await db.query(query.view, query.params);
html.push(['ID', 'Key'].join('\t'));
html.push(['----', '--------'].join('\t'));
docs.rows.forEach(row => {
html.push([row.id, row.key].join('\t'));
})
view_result.innerText = html.join('\n');
} catch (e) {
console.log('err: ' + e);
}
}
// canned test documents
function getDocsToInstall() {
return [{
_id: "1",
name: 'Test_01',
level: 1,
completed: false
},
{
_id: "2",
name: 'Test_02',
level: 2,
completed: true
},
{
_id: "3",
name: 'Test_01',
level: 3,
completed: false
},
{
_id: "4",
name: 'Test_4',
level: 3,
completed: true
},
{
_id: "5",
name: 'Test_05',
level: 2,
completed: true
},
{
"_id": "_design/my_index",
"views": {
"completed_str": {
"map": `function (doc) {
emit(doc.completed + '/' + doc.level + '/')
}`
},
"completed_complex": {
"map": `function (doc) {
emit([doc.completed,doc.level])
}`
}
}
}
]
}
let db;
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
return db.bulkDocs(getDocsToInstall());
}
(async() => {
try {
await initDb();
} catch (e) {
console.log(e);
}
})();
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<label for="completed">Completed:</label>
<select name="completed" id="completed">
<option value="true">True</option>
<option value="false">False</option>
</select>
<label for="level">Level:</label>
<select name="level" id="level">
<option value="0">0</option>
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="3">3</option>
</select>
<label for="view">View:</label>
<select name="view" id="view">
<option value="complex">Complex Key</option>
<option value="simple">Simple String Key</option>
</select>
<button id="query" onclick="query()">Query</button>
<div style='margin-top:2em'></div>
<pre id='view_result'>
</pre>

ReactJs: Passing a prop and using it within a map()

I'm trying to take a user inputted code and compare it to code within my database. Right now I can bring the code and display it outside the map function but when I try to add it, it doesn't work. here is my database:
[
{
"dwelling_code": "ABC-XYZ",
"dwelling_name": "Neves Abode",
"has_superAdmin": true,
"room": []
}
This is the parent component:
class Dwel2 extends Component {
state = {
house: [],
selectedMovie: null,
data: "ABC-XYZ"
}
componentDidMount() {
fetch('Removed for question', {
method: 'GET',
headers: {
}
}).then(resp => resp.json())
.then(resp => this.setState({ house: resp }))
.catch(error => console.log(error))
}
houseClicked = h => {
console.log(h)
}
render() {
return <div>
<EnterCode dataFromParent={this.state.data}
house={this.state.house}
houseClicked={this.house} />
</div>
}
}
This is the child component:
function EnterCode(props) {
return (
<div>
<div>
*THIS BIT DISPLAYS THE CODE*{props.dataFromParent}
</div>
{props.house.map(house => {
var test = house.dwelling_name
var code = house.dwelling_code
if (code === {props.dataFromParent}) {
test = "Test"
}
return (
<React.Fragment>
<div>{test}</div>
</React.Fragment>
)
})}
</div>
)
}
I just want to compare the code in the database to the code defined in the parent component. Here is the error that's coming up this is in the child component.
Line 17:31: 'dataFromParent' is not defined no-undef
You made a tiny mistake in the if statement. You put the props.dataFromParent in brackets, which in the context of JSX would be required, but in the context of JS means creating an object, which is clearly wrong.
if (code === props.dataFromParent) {
test = "Test"
}
Hope this helps :)

Emberjs Power select dynamic options and selectors

I'm struggling a bit with the proper pattern to use here. I have a model which represents a power selector called selector, each selector has a hasMany with selectorOption which makes up the options for the selector
I then have a dashboardItem model which loops over each selector and implements it.
route.js
export default Route.extend({
model(params) {
return RSVP.hash({
dashboard: get(this, 'store').findRecord('dashboard', params.dashboard_id),
selectors: get(this, 'store').findAll('selector'),
});
},
setupController(controller, models) {
controller.setProperties(models);
},
});
template.hbs
{{#each selectors as |selector|}}
<div class="column is-12 object-item">
<div class="card">
<header class="card-header">
<p class="card-header-title">
{{selector.title}}
</p>
</header>
<div class="card-content">
{{#power-select-multiple
placeholder="Vision"
options=selector.selectorOptions
searchEnabled=false
onchange=(action 'something...') as |option|}}
{{option.title}}
{{/power-select-multiple}}
</div>
</div>
</div>
{{/each}}
I'm not sure what to do on the onchange, either with a custom function or using built in tools of power-select.
Each selector is a multi-selector.
This works correctly to the point that I can create any number of selectors and they display on the front end with their correct options as expected.
How should I go about saving the options the users choose against the dashboardItem?
Here is a section from the database which shows the models and their relationships. Note there is currently no relationship between a selector and a dashboardItem (Maybe there should be though?)
{
"selectorOptions" : {
"-Kyc7on207d_IxnNw2iO" : {
"title" : "Apple",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7qC9_uxFgXP9c7hT" : {
"title" : "Orange",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7qqZPMikoG1r3r5g" : {
"title" : "Bannana",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7uZu8MTfUdH70cBR" : {
"title" : "Blue",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
},
"-Kyc7vJC3ImzVOEraALx" : {
"title" : "Green",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
},
"-Kyc7wCrqDz8CD_I-dYy" : {
"title" : "Red",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
}
},
"selectors" : {
"-Kyc7nG9Bz3aEGLked8x" : {
"title" : "Fruits",
"selectorOptions" : {
"-Kyc7on207d_IxnNw2iO" : true,
"-Kyc7qC9_uxFgXP9c7hT" : true,
"-Kyc7qqZPMikoG1r3r5g" : true
}
},
"-Kyc7rtTPTMJxAPacg-L" : {
"title" : "Colours ",
"selectorOptions" : {
"-Kyc7uZu8MTfUdH70cBR" : true,
"-Kyc7vJC3ImzVOEraALx" : true,
"-Kyc7wCrqDz8CD_I-dYy" : true
}
}
}
}
The solution was to not fight against relationships with basic array storage.
For example
Base
export default Model.extend({
title: attr('string'),
visionOptions: hasMany('vision-option'),
});
Bases Options
export default Model.extend({
title: attr('string'),
vision: belongsTo('vision'),
});
The model to save the selected objects on
export default Model.extend({
//...
visionOptions: hasMany('vision-option', {async: true}),
//...
});
The component to handle saving, and selecting the correct objects
export default Component.extend({
tagName: "",
classNames: "",
selectedVisions: computed('dashboardItem.visionOptions', function () {
const visionId = this.get('vision.id');
const options = this.get('dashboardItem.visionOptions');
return options.filterBy('vision.id', visionId);
}),
actions: {
addVision(newList) {
let dashboardItem = get(this, 'dashboardItem');
let options = get(this, 'selectedVisions');
options.forEach(function (me) {
if (!newList.includes(me)) {
dashboardItem.get('visionOptions').removeObject(me);
}
});
newList.forEach(function (me) {
if (!options.includes(me)) {
dashboardItem.get('visionOptions').pushObject(me);
}
});
dashboardItem.save().then(() => {
dashboardItem.notifyPropertyChange('visionOptions')
});
}
}
});
Template to render power-select
{{#power-select-multiple
placeholder=""
options=vision.visionOptions
searchEnabled=false
selected=selectedVisions
onchange=(action 'addVision') as |vision|}}
{{vision.title}}
{{/power-select-multiple}}
This allows there to be an unknown number of "visions", with an unknown number of "visionObjects" to be loaded and saved.
The notifyPropertyChange is required to update the computed property so the frontend renders when a user adds or removes a selected object. This is only awkward because there isn't a direct known database key.

Vue order list view

I've been working with vue js 1.27 on a project and I need to be able to sort a list by numeric values, Alphabetic and reverse order.
I've been trying this all day with not really much progress.
I've left some of my previous code in my code sample to let you know what I've already attempted.
{% extends 'base.html.twig' %}
{% block body %}
<div id="wrap">
<h2>Select a category</h2>
<ul id="categorySelect">
<li v-for="cat in categories | orderBy reverse" #click="selectCategory(cat)" class="${cat.selectedCategory == category ? 'selected' : ''}">${cat.title}</li>
</ul>
</div>
{% endblock %}
{% block javascripts %}
<script type="text/javascript">
Vue.config.delimiters = ['${', '}'];
new Vue({
el: '#wrap',
data: {
//reverse: -1,
wasClicked: true,
selectedCategory: null,
categories: [{
title: 'ALL',
category: null
},
{
title: 'CATE',
category: 'sport'
},
{
title: 'DOG',
category: 'sport'
},
{
title: 'SPEED',
category: 'sport'
},
{
title: 'CAT',
category: 'sport'
},
{
title: 'SPORT',
category: 'sport'
},
{
title: 'ART',
category: 'sport'
},
{
title: 'PEOPLE',
category: 'people'
},
{
title: 'CAR',
category: 'car'
}]
},
filters: {
categoryFilter: function (infoBlocs) {
return this.wasClicked ? this.categories : {};
},
caseFilter: function () {
if (this.wasClicked) {
return this.reverseArray();
}
return this.alphaSortByKey(this.categories, 'category');
},
reverse: function(value) {
// slice to make a copy of array, then reverse the copy
return value.slice().reverse();
}
},
methods: {
selectCategory: function(category) {
//this.wasClicked =! this.wasClicked;
//this.categories = this.alphaSortByKey(this.categories, 'category');
// if (this.reverse) {
// this.categories = this.alphaSortByKey(this.categories, 'category');
// }
// else {
// this.categories = this.reverseArray();
// }
if (this.reverse) {
this.categories = this.alphaSortByKey(this.categories, 'category');
this.reverse = false;
}
else {
this.categories = this.reverseArray();
//this.reverse = true;
}
},
alphaSortByKey: function (arr, key) {
arr.sort(function (a, b) {
if (a[key] < b[key])
return -1;
if (a[key] > b[key])
return 1;
return 0;
});
return arr;
},
reverseArray: function () {
return this.categories.reverse();
},
changeOrder: function (event) {
var self = this;
self.reverse = self.reverse * -1
var newItems = self.categories.slice().sort(function (a, b) {
var result;
if (a.name < b.name) {
result = 1
}
else if (a.name > b.name) {
result = -1
}
else {
result = 0
}
return result * self.reverse
})
newItems.forEach(function (item, index) {
item.position = index;
});
this.categories = newItems;
}
}
});
</script>
{% endblock %}
Here is a fiddle with working functionality to sort and reverse the order of your array. For reverse I just used the built in reverse() Javascript function. For the alphanumeric sort I borrowed the solution from this answer: https://stackoverflow.com/a/4340339/6913895
https://jsfiddle.net/n1tbmgo9/
Html:
<div id="wrap">
<h2>Select a category</h2>
<button #click="sort">
Sort alphanumeric
</button>
<button #click="reverse">
Reverse list
</button>
<ul id="categorySelect">
<li v-for="cat in categories">${cat.title}</li>
</ul>
</div>
Javascript:
Vue.config.delimiters = ['${', '}'];
new Vue({
el: '#wrap',
data: {
selectedCategory: null,
categories: [{
title: 'ALL',
category: null
},
{
title: 'CATE',
category: 'sport'
},
{
title: 'DOG',
category: 'sport'
},
{
title: 'SPEED',
category: 'sport'
},
{
title: 'CAT',
category: 'sport'
},
{
title: 'SPORT',
category: 'sport'
},
{
title: 'ART',
category: 'sport'
},
{
title: 'PEOPLE',
category: 'people'
},
{
title: 'CAR',
category: 'car'
}]
},
methods: {
sort: function () {
this.categories.sort(this.sortAlphaNum);
},
reverse: function () {
this.categories.reverse();
},
sortAlphaNum: function (a,b) {
var reA = /[^a-zA-Z]/g;
var reN = /[^0-9]/g;
var aA = a.title.replace(reA, "");
var bA = b.title.replace(reA, "");
if(aA === bA) {
var aN = parseInt(a.title.replace(reN, ""), 10);
var bN = parseInt(b.title.replace(reN, ""), 10);
return aN === bN ? 0 : aN > bN ? 1 : -1;
} else {
return aA > bA ? 1 : -1;
}
}
}
});
The built in reverse() function is straightforward so I will elaborate on the sortAlphaNum() sort function. That function is passed into the sort() function and must return either 1, 0, or -1 to indicate if the objects passed in should be moved in a particular direction in the array.
The variables reA and reN are regexes to identify alphabetic and numeric characters, respectively.
First the function removes all alphabet characters from the titles of the two objects passed in and compares them for equality.
var aA = a.title.replace(reA, ""); and
var bA = b.title.replace(reA, "");
If they are not equal then it means we have alphabet characters (as opposed to just numeric input) and we can sort them accordingly.
return aA > bA ? 1 : -1;
If the titles with alphabet characters stripped are equal (if(aA === bA)) then we remove numeric digits from the object titles (leaving non-numeric characters).
var aN = parseInt(a.title.replace(reN, ""), 10);
var bN = parseInt(b.title.replace(reN, ""), 10);
Then we compare the resulting variables and return the appropriate sorting value (1, 0, -1).
return aN === bN ? 0 : aN > bN ? 1 : -1;
create a computed property where you manually sort it and then loop for that instead the data prop
There is my List Component implementation with client-side order implementation:
<template>
<div>
<table class="table table-bordered" v-if="data.length">
<thead>
<tr>
<th v-for="(colValue, colKey) in cols" :key="colKey">
<a #click="sort(colKey)" href="javascript:void(0)">
{{colValue}}
<icon :name="(sortColumn === colKey) ? (sortAsc ? 'sort-down' : 'sort-up') : 'sort'"></icon>
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="(colValue, colKey) in cols" :key="row.id + colKey">{{row[colKey]}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import _ from 'lodash';
import apiServer from '#/utils/apiServer'; //
export default {
name: 'List',
data() {
return {
data: [],
sortColumn: '',
sortAsc: true
};
},
props: {
cols: {
type: Object,
required: true
},
apiEndpoint: {
type: String,
required: true
}
}
created() {
this.fetchData();
},
watch: {
'$route': 'fetchData'
},
methods: {
async fetchData() {
const response = await apiServer.get(this.apiEndpoint);
this.data = response.data;
},
sort(colKey) {
this.data = _.sortBy(this.data, [colKey]);
if (this.sortColumn === colKey) {
if (!this.sortAsc) {
this.data = _.reverse(this.data);
}
this.sortAsc = !this.sortAsc;
} else {
this.sortAsc = false;
}
this.sortColumn = colKey;
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
Usage example
<template>
<div>
<h1>Orders</h1>
<List :cols="cols" api-endpoint="/orders" title="Orders" />
</div>
</template>
<script>
import List from '#/components/List.vue';
export default {
name: 'OrderList',
components: { List },
data() {
return {
cols: {
id: 'Id',
title: 'Title',
created_at: 'Created at'
}
};
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>