Given my onClick={() => this.activeToggle("Discharge")} in the outer most div.
Is it possible to override the onClick() when clicking on one of the two checkboxes?
This is part of a accordion and it works great, but I would like to ignore the onClick event when I check one of the two checkboxes.
EXAMPLE (bit patchy)
const stateDischarge = {
active: {
display: 'inherit'
},
inactive: {
display: 'none'
}
};
var Preferences = React.createClass({
.
.
.
activeToggle: function (panel) {
event.stopPropagation(); // <= this is there I am trying the suggestion. but not working, so trying something else.
switch (panel) {
case "Demographics":
this.setState({
activeDemographics: !this.state.activeDemographics
});
break;
case "Admission":
this.setState({
activeAdmission: !this.state.activeAdmission
});
break;
case "Discharge":
this.setState({
activeDischarge: !this.state.activeDischarge
});
break;
default:
//Statements executed when none of the values match the value of the expression
break;
}
},
.
.
.
const stateDischarge = this.state.activeDischarge ? styles.active : styles.inactive;
render: function() {
<div className="panel panel-info">
<div className="panel-heading" onClick={() => this.activeToggle("Discharge")}>
<h4 className="panel-title pull-left">
<a data-toggle="collapse" data-parent="#accordion">Discharge</a>
</h4>
<div className="pull-right">
<label className="checkbox-inline"><input type="checkbox" value=""/>Admission</label>
<label className="checkbox-inline"><input type="checkbox" value=""/>Non-Admission</label>
</div>
<div className="clearfix"></div>
</div>
<div id="collapseOne" className="panel-collapse" style={stateDischarge}>
<div className="panel-body">
<div className="col-sm-6 col-md-6">
//Content in Accordion pannel
</div>
</div>
</div>
</div>
}
});
module.exports = Preferences;
Related
I made an object to manage the candidate education in online marketplace
let education ={
tabs:{},
render:function(){
document.querySelector('#education-tabs').innerHTML = ''
let container =``;
Object.values(this.tabs).map((tab)=>{
container += `
<div class="education-tab-container">
<div class="education-tab-right">
<div class="big-title">
${tab.faculty}
</div>
<div class="medium-title">
${tab.department}
</div>
<div class="small-title">
${tab.university}
</div>
</div>
<div class="education-tab-left">
<div class="date">
${tab.from || ''} ${tab.to || ''}
</div>
<div class="close" data-id="${tab.id}">
<span>x</span>
</div>
</div>
</div>
`
})
document.querySelector('#education-tabs').innerHTML = container
},
deleteTab:function(id){
delete this.tabs[id];
this.render();
},
add:async function(payload){
this.tabs = {...this.tabs,[payload.id]:payload}
this.render();
}
}
This updates the UI every time I add a new tab to this object.
The problem is when I try to remove a tab: sometimes I get the data-id attribute of the tab that I made and sometimes it returns null.
here is my function
const triggerListener = ()=>{
$("#education-tabs").find(".education-tab-left .close").click((e)=>{
if(e.target.getAttribute('data-id')){
alert(e.target.getAttribute('data-id'))
}
});
so is there a way to solve this problem?
I want to filter my checkboxes I search it on internet there was information but I couldn't work it with my code.
This is the webpage
I want when you click on the checkbox it must be same as the category.
This is some code of the checkbox:
<div>
<input class="form-check-input checkboxMargin" type="checkbox" value="All" v-model="selectedCategory">
<p class="form-check-label checkboxMargin">All</p>
</div>
This is my grey box layout:
<div class="col-sm-12 col-md-7">
<div class="card rounded-circle mt-5" v-for="item of items" :key="item['.key']">
<div>
<div class="card-body defaultGrey">
<h5 class="card-title font-weight-bold">{{ item.name }}</h5>
<div class="row mb-2">
<div class="col-sm ">
<div class="row ml-0"><h6 class="font-weight-bold">Job:</h6><h6 class="ml-1">{{ item.job }}</h6></div>
<div class="row ml-0"><h6 class="font-weight-bold">Category:</h6><h6 class="ml-1">{{ item.categories }}</h6></div>
<div class="row ml-0"><h6 class="font-weight-bold">Location:</h6><h6 class="ml-1">{{ item.location }}</h6></div>
<div class="row ml-0"><h6 class="font-weight-bold">Niveau:</h6><h6 class="ml-1">{{ item.niveau }}</h6></div>
<div class="row ml-0"><h6 class="font-weight-bold">Availability:</h6><h6 class="ml-1">{{ item.availability }}</h6></div>
<h6>{{ item.info }}</h6>
<div class="row ml-0"><h6 class="font-weight-bold">Posted:</h6><h6 class="ml-1">{{ item.user }}</h6></div>
</div>
</div>
<div class="row">
<div class="col-xs-1 ml-3" v-if="isLoggedIn">
<router-link :to="{ name: 'InternshipDetails', params: {id: item['.key']} }" class="btn bg-info editbtn">
Details
</router-link>
</div>
<div class="col-xs-1 ml-3 mr-3" v-if="isLoggedIn && item.user == currentUser">
<router-link :to="{ name: 'Edit', params: {id: item['.key']} }" class="btn btn-warning editbtn">
Edit
</router-link>
</div>
<div class="col-xs-1" v-if="isLoggedIn && item.user == currentUser">
<button #click="deleteItem(item['.key'])" class="btn btn-danger dltbtn">Delete</button>
</div>
</div>
</div>
</div>
</div>
</div>
And I have my object here but how can I filter my grey boxes with category:
selectedCategory: []
Use computed properties:
computed: {
filteredItems(){
if(this.selectedCategory.length == 0) return this.items;
return this.items.filter(el => {
for(let i = 0; i < this.selectedCategory.length; i++){
if(el.categories == this.selectedCategory[i]) return el;
}
});
}
}
Then you go for v-for="item of filteredItems"
I didnt tested it. If you provide me more code i could help you more
You need to create one-way binding between your CategoryCheckBox component and your ListCard component.
Because you provided separated code when I can not reproduce it to give you a solution based on your own, I suggest this example to explain my solution.
Step One:
You have many ways to CRUD your items using a Plugins or Vuex or global instance, in my example I used global instance in main.js
Vue.prototype.$myArray = ["Books", "Magazines", "Newspaper"];
I assume you created your items data in the ListCards component
Step Two:
You need to add #change event in your checkbox to handle checked and unchecked states. I used static data (Book) for value.
<label>
<input type="checkbox" value="Books" #change="handleCategory($event)" /> Books
Now, let's implement handleCategory method, but before that, as we know that Checkbox and ListCards are independents which means we need to define a bus to create event communication between them and this is your issue so we define the bus inside the main.js
Vue.prototype.$bus = new Vue({});
Now we define handleCategory like this :
methods: {
handleCategory(e) {
this.$bus.$emit("checkCategory", e);
}
}
Step Three:
How can our ListCards listen to this event ?
By call $bus.$on(..) when the component is created ( Hope you know what Vue lifecycle methods mean )
created() {
this.$bus.$on("checkCategory", e => this.checkCategory(e));
},
When the user click the check box the component runs handleCategory then runs checkCategory inside ListCard.
Inside my ListCard, I have categories and originalCategories as data
data() {
return {
categories: this.$myArray,
originalCategories: this.$myArray
};
},
and a template :
<ul v-for="(category,index) in categories" :key="index">
<CategoryItem :categoryName="category" />
</ul>
and a created method ( lifecycle )
created() {
// $bus is a global object to communicate between components
this.$bus.$on("checkCategory", e => this.checkCategory(e));
},
and our filtering methods :
methods: {
checkCategory(e) {
let target = e.target;
let value = e.target.value;
target.checked
? this.filterCatergories(value)
: (this.categories = this.originalCategories);
},
filterCatergories(value) {
this.categories = this.categories.filter(val => val === value);
}
}
What's important for you is :
this.categories.filter(val => val === value); //will not do nothing
const categories=this.categories.filter(val => val === value); //will update the view.
And you can change the code or make it better and simple.
#Update
You can also use computed properties but because we have a parameter here which is the category name we need get and set.
computed: {
filteredItems: {
get: function() {
if (this.selectedCategory.length == 0) return this.items;
return this.items.filter(value => {
for (const category of this.selectedCategory) {
if (value == category) return value;
}
});
},
set: function(category) {
this.selectedCategory.push(category);
}
}
},
methods: {
checkCategory(e) {
let target = e.target;
let value = e.target.value;
target.checked
? (this.filteredItems = value)
: this.selectedCategory.pop(value);
}
}
So I've got two input boxes that I want to be able to toggle (hide) between by clicking on them. They populate a table below them. I know it's not difficult but can't seem to make it happen easily.
It's all happening in the one component. Something like this:
<p id="toggle">
<span> Employer </span>
<span> Location </span>
</p>
<div id="left">..input box 1</div>
<div id="right">..input box 2</div>
What's the function I'd need to implement it? Thanks!
Your issue is indeed not very difficult.
Here is a solution with a function component:
const MyComponent = (props) => {
const [selected, setSelected] = useState(0)
return (
<div>
<p id="toggle">
<span onClick={() => setSelected(0)}> Employer </span>
<span onClick={() => setSelected(1)}> Location </span>
</p>
{(selected === 0) && <div id="left"> ..input box 1</div>}
{(selected === 1) && <div id="right"> ..input box 2</div>}
</div>
)
}
Here is a solution with a class component:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
selected: 0
}
}
render() {
return (
<div>
<p id="toggle">
<span onClick={() => this.setState({ selected: 0 })}> Employer </span>
<span onClick={() => this.setState({ selected: 1 })}> Location </span>
</p>
{(selected === 0) && <div id="left"> ..input box 1</div>}
{(selected === 1) && <div id="right"> ..input box 2</div>}
</div>
)
}
}
If I'm understanding you're wanting to toggle the display of the div elemements on clicking the span elements (why not use anchors they're better suited). Below is a simple way to implement this,
(() =>
{
const togglers = document.querySelectorAll('#toggle span');
const togglees = document.querySelector('#toggle').parentNode.querySelectorAll('div');
togglers.forEach((toggler, index) =>
{
toggler.addEventListener('click', () =>
{
togglees.forEach(togglee => togglee.style.display = 'none');
togglees[index].style.display = 'block';
}, true);
});
})();
/* HIDE THOSE TOGGLEES BY DEFAULT CHANGE THIS!!!!!!!! */
div > div
{
display: none;
}
<div>
<p id="toggle">
<span> Employer </span>
<span> Location </span>
</p>
<div id="left"> ..input box 1</div>
<div id="right"> ..input box 2</div>
</div>
I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>
I can't make an accordion with KnockoutJS, and Bootstrap to work properly. I have defined it like so:
<div class="panel-group" id="accordion" data-bind="foreach: Advertisers()">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<span data-toggle="collapse" data-bind="html: $data, attr: { 'data-target': '#' + $data }"></span>
</h4>
</div>
</div>
<div class="panel-collapse collapse" data-parent="#accordion" data-bind="attr: { id: $data }">
<div class="panel-body">
...content...
"Advertisers" is an observable array of strings, and hence $data is a string. I get one "row" for each advertiser.
All rows are initially collapsed, and clicking a row expands the content below. So far so good.
The problem is that when I click another row I would expect the previous expanded to collapse, but that's not happening. (I couldn't make a fiddle to work either, with Bootstrap and KnockoutJS...)
Edited the code.
What about a simple custom binding, which also allows you to unclutter your view a bit:
ko.bindingHandlers.bootstrapAccordion = {
init: function(elem, value, allBindings) {
var options = ko.utils.unwrapObservable(value()),
handleClass = '[data-toggle]',
contentClass = '.collapse',
openItem = ko.utils.unwrapObservable(options.openItem) || false,
itemClass = '.' + ko.utils.unwrapObservable(options.item) || '.accordion-group',
items = $(elem).find(contentClass);
// toggle: false required to hide items on load
items.collapse({ parent: elem, toggle: false });
if (openItem > -1) items.eq(openItem).collapse('show');
// if the array is dynamic, the collapse should be re-initiated to work properly
var list = allBindings.get('foreach');
if (ko.isObservable(list)) {
list.subscribe(function() {
$(elem).find(contentClass).collapse({ parent: elem, toggle: false });
});
}
$(elem).on('click', handleClass, function() {
$(elem).find(contentClass).collapse('hide');
$(this).closest(itemClass).find(contentClass).collapse('show');
});
}
};
This binding takes 2 parameters (className for container, and optionally, an item to open on load), eg: bootstrapAccordion: {item: 'panel-group', openItem: 0}, and should be set on the same element which has a foreach binding. It assumes that collapsible sections have a collapse class, and the handles to toggle them have a data-toggle attribute.
See it in action here:
http://jsfiddle.net/pkvn79h8/22/
I extended Tyblitz's example above to include support for changing an icon (eg, +/-, up/down arrow) and support for moving to the next panel by applying data-open-next attribute to whatever should move to next panel on click.
ko.bindingHandlers.bootstrapAccordion = {
init: function (elem, value, allBindings) {
var options = ko.utils.unwrapObservable(value()),
handleClass = '[data-toggle]',
contentClass = '.collapse',
openedClass = ko.utils.unwrapObservable(options.openedClass) || 'fa-minus',
closedClass = ko.utils.unwrapObservable(options.closedClass) || 'fa-plus',
openCloseToggleClasses = openedClass + ' ' + closedClass,
openItem = ko.utils.unwrapObservable(options.openItem) || false,
itemClass = '.' + (ko.utils.unwrapObservable(options.item) || 'accordion-group'),
items = $(elem).find(contentClass);
var initializeItems = function(items) {
// toggle: false required to hide items on load
items.collapse({ parent: elem, toggle: false });
if (openItem > -1) {
items.eq(openItem).collapse('show');
items.eq(openItem).closest(itemClass).find('.panel-heading').find('i').toggleClass(openCloseToggleClasses);
items.eq(openItem).closest(itemClass).find('.panel-heading').addClass('active');
}
}
initializeItems(items);
// if the array is dynamic, the collapse should be re-initiated to work properly
var list = allBindings.get('foreach');
if (ko.isObservable(list)) {
list.subscribe(function () {
initializeItems($(elem).find(contentClass));
});
}
$(elem).on('click', handleClass, function () {
$(elem).find(contentClass).collapse('hide');
$(this).closest(itemClass).find(contentClass).collapse('show');
$(this).closest(itemClass).parent().find('.panel-heading i').removeClass(openCloseToggleClasses);
$(this).closest(itemClass).parent().find('.panel-heading i').addClass(closedClass);
$(this).closest(itemClass).parent().find('.panel-heading').removeClass('active');
if ($(this).closest(itemClass).find('.panel-collapse').attr('aria-expanded') === "true") {
$(this).closest(itemClass).find('.panel-heading i').toggleClass(openCloseToggleClasses);
$(this).closest(itemClass).find('.panel-heading').addClass('active');
}
});
$(elem).on('click', '[data-open-next]', function () {
$next = $(this).closest(itemClass).next(itemClass).find(handleClass);
if ($next.length) {
$next.click();
} else {
$same = $(this).closest(itemClass).find(contentClass);
$same.collapse('hide');
$same.parent().find('.panel-heading i').removeClass(openCloseToggleClasses);
$same.parent().find('.panel-heading i').addClass(closedClass);
$same.parent().find('.panel-heading').removeClass('active');
}
});
}
};
Sample markup to use with this binding:
<div data-bind="foreach: listOfThings, bootstrapAccordion: { openItem: 0 }">
<div class="accordion-group">
<div class="panel panel-default" style="cursor: pointer;" data-toggle>
<div class="panel-heading">
<i class="fa fa-plus fa-pull-left fa-2x"></i>
<h3 data-bind="text: name">Title of expander</h3>
</div>
</div>
<div class="panel-collapse collapse">
<div class="panel-body">
<div class="clearfix" data-accordion-content>
<!-- content goes here -->
<!-- ko if: $index() < $parent.listOfThings().length -1 -->
<button data-open-next>Next Thing</button>
<!-- /ko -->
</div>
</div>
</div>
</div>
</div>
I would feel bad not contributing back :)