Vue state change is applied to previous focusses context - javascript

So i am working on a fieldset pagination in a form i'm making with Vue. The issue i now have with my pagination is that the state-change of the visibility of the pagination controls is applied to the previous fieldset, instead of the current one.
My form is constructed like this:
<template>
<form>
<fieldset id="one" v-show="activePage == 'one'">
<input />
<pagination-ctrl #paginate="paginate" />
</fieldset>
<fieldset id="two" v-show="activePage == 'two'">
<input />
<pagination-ctrl #paginate="paginate" />
</fieldset>
<fieldset id="three" v-show="activePage == 'three'">
<input />
<pagination-ctrl #paginate="paginate" />
</fieldset>
</form>
</template>
<script lang="coffee">
import Pagination from '#/components/Pagination.vue'
import FormInput from '#/components/FormInput.vue'
export default
name: 'Form'
data: ->
activePage: 'one'
components:
'pagination-ctrl': Pagination
'input': FormInput
methods:
paginate: (data) ->
#activePage = data
</script>
Pagination.vue contains the buttons to switch between the active fieldset and looks like this:
<template>
<div class="btn-group" role="button" v-on:click="paginate" ref="btn-group">
<button class="ui-button prev" rel="prev" :disabled="disablePrev">Previous</button>
<button class="ui-button next" rel="next" :disabled="disableNext">Next</button>
</div>
</template>
<script lang="coffee">
export default
name: 'FormControl'
data: ->
pages: null
disablePrev: true
disableNext: true
methods:
accumelatePages: ->
fieldsetNode = #$refs['btn-group'].parentNode
formNode = fieldsetNode.parentNode
# cast fieldsets to true array in favor of HTMLNodeCollection
#pages = Array.prototype.slice.call(formNode.getElementsByTagName('fieldset'))
determineButtonVisibility: (item) ->
currIndex = #pages.findIndex((node)->
node.getAttribute('id') is item
)
#disablePrev =
if currIndex > 0
false
else true
#disableNext =
if currIndex < (#pages.length - 1)
false
else true
paginate: (e) ->
e.preventDefault()
node = e.target
if node.getAttribute('rel') is 'next'
activeNode = node.parentNode.parentNode.nextElementSibling
if node.getAttribute('rel') is 'prev'
activeNode = node.parentNode.parentNode.previousElementSibling
if activeNode?
nodeId = activeNode.getAttribute('id')
#$emit('paginate', nodeId)
#determineButtonVisibility(nodeId)
mounted: ->
#accumelatePages()
#determineButtonVisibility(#pages[0].getAttribute('id'))
</script>
The idea is that when you click a button, determineButtonVisibility() determines the position of the current fieldset in relation to the surrounding fieldsets, and sets the display of the buttons accordingly. The problem however is that this works perfectly fine, but these display-state's are applied to the fieldset you just navigated away from.
So if i click on the next button in fieldset one, the state-change is applied to fieldset one (old context) instead of fieldset two (new context).
Perhaps i'm now overlooking something really obvious or heading in a completely wrong direction, but i've already spent way more time on this then i actually should sho i'm kind of lost atm.

I believe if you pass activePage as a prop from Form.vue into your Pagination.vue, and then set a watcher for when that value changes, you can have each instance of pagination-ctrl dynamically run determineButtonVisibility on change.
<pagination-ctrl #paginate="paginate" :activePage="activePage" />

Related

Detect clicks inside a Div but outside of a particular child Div in VueJS

I have a page in which I have MCQ Options which looks like this:
This is the HTML Code for the options parts till now:
<div v-for="(option, index) in optionsForFrontend">
<div class="option">
<div class="radio-check-item">
<input type="radio"
name="question"
:value="index"
v-model="picked"
v-if="question.metadata.choices === 'Single'">
<input type="checkbox"
name="question"
:value="index"
v-model="picked"
v-if="question.metadata.choices === 'Multiple'">
</div>
<div class="divider">
</div>
<div class="content-item">
<vue-markdown :source="option.option"></vue-markdown>
</div>
</div>
</div>
I want to select the options by clicking on the option content. So I put a :click="functionToSelectOption" on the outer div ("option") containing checkbox and option content.
The problem is when I select the checkbox, it gets selected and the function "functionToSelectOption" gets called as well since the checkbox is inside the "option" div.
I want to be able to detect a click inside the "option" div but outside radio/checkbox div so I can call the function to toggle the option.
I mostly found similar questions under jquery tags and nothing with VueJS.
I would avoid event.stopPropagation(). All kinds of things might be listening higher up in the tree. For instance, you might want an onclick on the window to close a modal dialog if the user clicks outside the dialog. The much less radical way to do this is to look at event.target. This jsfiddle tests (event.target == event.currentTarget) so it only reacts to clicks on the parent div.
Markup
<div id="ctr">
<div v-for="(option,index) in options"
style="margin:10px;padding:10px;border:solid;border-radius:4px"
#click="parent"
>
<input type="checkbox" :name="'invidiousChoice' + index" #click='child'> {{option.label}}
</div>
</div>
JS
var vm = new Vue({
el:"#ctr",
data : {
options:[{label:'terrible option'},{label:'disastorous option'}]
},
methods: {
parent: function(event) {
if(event.target == event.currentTarget)
alert( 'parent clicked');
}
}
})
You can use event.stopPropagation() in child element.
Here is working JSFiddle Solution: Link
HTML
<div id="app">
<div #click="parent">
Parent
<div>
<input type="checkbox" #click="child">
</div>
</div>
{{message}}
JS
new Vue({
el: '#app',
data: {
message: ''
},
methods: {
parent: function() {
this.message = 'parent clicked';
},
child: function() {
event.stopPropagation()
}
}
});

React Component "closing" when lost focus

I'm trying to create a select2 style component in React.
I have got 90% functionality down, the one bit I just can't fathom is hiding the result box when the user clicks away
The render method is:
render() {
let resultBlock;
if (this.state.showSearch) {
resultBlock = (
<div className="search-input-container" onBlur={this.onBlur}>
<div className="search-input-results">
<input
type="text"
name={this.props.name}
placeholder={this.props.placeholder}
className="form-control"
onChange={this.inputKeyUp}
autoComplete="false" />
<ul>
{this.state.items.map((item, i) => <li key={i} data-value={item.id} onClick={this.itemSelected} className={item.isSelected ? 'selected' : ''}>{item.text}</li>)}
</ul>
</div>
</div>
);
}
let displayBlock;
if (this.props.value.text) {
displayBlock = this.props.value.text;
} else {
displayBlock = <span className="placeholder">{this.props.placeholder}</span>;
}
return (
<div className="form-group">
<label htmlFor={this.props.name}>{this.props.label}:</label>
<div className="form-input">
<div className="searchable-dropdown" onClick={this.revealSearch}>
{displayBlock}
<div className="arrow"><i className="fa fa-chevron-down" aria-hidden="true" /></div>
</div>
{resultBlock}
</div>
</div>
);
}
I've tried moving onBlur={this.onBlur} around, but it only fires if the <input... had focus before one clicked away.
It can't be that complicated, the only approach I thought of, is attaching a global click handler to the page, and diff'ing clicks to understand if a user hasn't clicked on my component. But this seems over engineered.
How can this be achieved?
I achieved this functionality by:
Putting this in the constructor:
this.windowClick = this.windowClick.bind(this);
(From what dfsq said) Put this in componentDidMount:
if (window) {
window.addEventListener('click', this.windowClick, false);
}
This event handler:
windowClick(event) {
event.preventDefault();
if (event.target.classList.contains('searchable-marker')) {
return;
} else {
this.setState({
showSearch: false
});
}
}
Where searchable-marker is just a class I put on all the div's, ul's, li's and inputs to make sure that if I clicked one of these, it wouldn't close the the box.
Adding the unmount:
componentWillUnmount() {
window.removeEventListener('click', this.windowClick, false);
}
what you can try doing is onBlur you could change the value of this.state.showSearch = false and when this condition is satisfied add a className="hide" (hide{display: none}) by creating a custom function which returns a classname as a string.

Clear data object on route change

I have a data object that contains color item (string), and It's associated with radio input, where I have v-model on t.
I have Add To Cart button, which I display conditionally in template using v-if - so if color wasn't selected, the button won't be visible, otherwise it would be.
The problem is that color always keep one value, and If I switch to another product it would keep the value from previous product, and button would be visible instantly.
Is there a way to clear color string after route changes - there is $route.afterEach but I'm not sure how to use it here.
Thanks.
Code:
<div class="Radio">
<input
type="radio"
id="radio-{{ va.attributes.term_id }}"
name="variation"
value="{{ va.attributes.color_name }}"
v-model="color"
>
<label for="radio-{{ va.attributes.term_id }}"></label>
</div>
Stuff about buttons
<div v-if="color">
<button #click="addToBag" class="Btn Btn--primary Btn--expanded">Add to Bag</button>
</div>
<div v-else>
<button class="Btn Btn--primary Btn--expanded" disabled>Add to Bag</button>
</div>
There is data related to JS
data() {
return {
product: [],
shared: State.data,
color: ''
}
}
1.user a global event bus in the entry js
window.eventBus = new Vue();
2.add a event handler to this color picker component , ie in ready()
ready(){
var vm = this
eventBus.$on('clearColorPicker', function(){vm.color = ''})
}
3.trigger the clearColorPicker event in $route.afterEach by
eventBus.$emit('clearColorPicker')
This can help you to trigger some change to certain component during route change.
But I think In your case there is a better way like the changing of product trigger the color picker reset

Showing/hiding elements in React

having a little trouble here when trying to show or hide divs in react.
I have three classes:
1. RunningOrder Section
2. RunningOrder
3. Story
running order section is the main section, which renders the running order class, and running order renders the Story class.
I have a toggle switch in running order section which toggles the 'displayStory' state in running order and running order passes the 'displayStory' bool state as a prop to story.
toggle in RunningOrder Section:
_triggerChildDisplay: function () {
this.refs.child.displayStory();
},
<Toggle
tooltip = {'Auto Sequence'}
onToggle = {this._triggerChildDisplay}
/>
for (let storyId in ro.stories) {
allStories.push(<Story key={storyId} story={ro.stories[storyId]} displayStory={this.state.displayStory}/>)
};
state and prop in Running Order:
displayStory: function() {
this.setState({
displayStory: !this.state.displayStory
});
},
for (let storyId in ro.stories) {
allStories.push(<Story key={storyId} story={ro.stories[storyId]} displayStory={this.state.displayStory}/>)
};
render method of story
if(allItems.length > 0) {
return (
<div className="story-container">
<div className="story-label-container">
<div className="story-slug">
<span className="story-title">
{story.slug}
</span>
{/*<span className="storyItemLength">
{allItems.length}
</span>*/}
<span className="story-sequence-control">
<OverlayTrigger placement="top" overlay={autoSequenceTooltip}>
<Toggle
label = {<SequenceIcon />}
disabled = {allItems.length > 0? false : true}
tooltip = {'Auto Sequence'}
onToggle = {this.toggled}
/>
</OverlayTrigger>
</span>
</div>
</div>
{itemList}
</div>
);
} else {
return (
<div className={this.props.displayStory ? "story-container" : "hidden story-container"}>
<div className="story-label-container">
<div className="story-slug">
<span className="story-title">
{story.slug}
</span>
{/*<span className="storyItemLength">
{allItems.length}
</span>*/}
<span className="story-sequence-control">
<OverlayTrigger placement="top" overlay={autoSequenceTooltip}>
<Toggle
label = {<SequenceIcon />}
disabled = {allItems.length > 0? false : true}
tooltip = {'Auto Sequence'}
onToggle = {this.toggled}
/>
</OverlayTrigger>
</span>
</div>
</div>
{itemList}
</div>
);
}
}
I only want to be able to toggle the display of elements in story if the allItems array has length greater than 0, hence the two returns.
Running order loops and pulls data from a server, the running order class returns 3 items each of these items have multiple items in them rendered by story which i need to toggle the display of.
Now my code is working, but for some reason I can only toggle the display (the hidden css class) in the last element returned by running order. so if running order returned:
<div 1>
<items />
</div>
<div 2>
<items />
</div>
<div 3>
<items />
</div>
the display toggle only affects the items in div 3.
*******Update:** instead of a single toggle for all elements rendered by running order I have put the toggle switch in running order and it works fine, so it is something with call the reffs here: this.refs.child.displayStory();

jQuery if radio button is selected then proceed to next element

I'm very new to JavaScript and jQuery. I know my code is not the prettiest, but I'm trying to start somewhere.
I have a series of questions that are shown one at a time, and want to create some sort of validation to prevent the user from going to the next question if a radio button on the first button hasn't been selected.
HTML (I have four of these .questionContainers
<div class="questionContainer">
<div class="question">
How much storage do you need?
</div>
<div class="answers">
<ul>
<li>
<label>
<input type="radio" id="storage">1GB or less
</label>
</li>
<li>
<label>
<input type="radio" id="storage">More than 1GB
</label>
</li>
</ul>
</div>
<div class="btnContainer">
<div class="next">
<a class="btnNext">Next</a>
</div>
</div>
</div>
JavaScript
(function(){
var Questions = {
container : $('.questionContainer'),
init : function() {
this.start();
if($('input[type=radio]:checked').length > 0){
this.container.find('a.btnNext').on('click', this.next);
}
},
start : function() {
this.container.not(':first').addClass('hide');
},
next : function() {
var container = $('.questionContainer');
$(this).closest(container).hide(function(){
$(this).closest(container).next().show();
});
}
};
Questions.init();
})();
The specific line that isn't working:
if($('input[type=radio]:checked').length > 0) {
this.container.find('a.btnNext').on('click', this.next);
}
The Problem
When I add the if statement and click a radio button followed by next, it does not go to the next question. I am not receiving any errors in the console.
This binds to the click event only if something is checked at the time that the start function is called (not what you want - it will never be true unless you pre-select a radio button for the user without their action):
if($('input[type=radio]:checked').length > 0){
this.container.find('a.btnNext').on('click', this.next);
}
Try replacing it with this instead:
this.container.find('a.btnNext').on('click', function () {
if($('input[type=radio]:checked').length > 0){
this.next();
}
});
This way you always bind to the click event, but the function that is bound only allows next to be called if something is checked at the time that the function is called (i.e. when the next button is clicked).

Categories