How to detect which knob was changed in ionic 4 ion-range with duallKnobs? - ionic4

I'm using ionic 4 and ion-range with dualKnobs set to true. And I need to detect which knob (left or right) was updated and get its value. Any ideas?

A bit of hacky approach but it works.
Template-
<ion-range id="dual-range" dual-knobs pin color="dark" (ionChange)="knobChange($event)" (ionBlur)="afterChange($event)">
<ion-icon slot="start" size="small" name="brush"></ion-icon>
<ion-icon slot="end" name="brush"></ion-icon>
</ion-range>
TypeScript file -
...
export class DemoPage implements OnInit{
upperValue: number;
lowerValue: number;
changedKnob: string;
ngOnInit() {
this.lowerValue = 33;
this.upperValue = 60;
const dualRange = document.querySelector('#dual-range') as HTMLIonRangeElement;
dualRange.value = { lower: this.lowerValue, upper: this.upperValue };
}
knobChange(range: CustomEvent<RangeChangeEventDetail>) {
if (range.detail.value['lower'] !== this.lowerValue) {
this.lowerValue = range.detail.value['lower'];
this.changedKnob = 'lower';
} else if (range.detail.value['upper'] !== this.upperValue) {
this.upperValue = range.detail.value['upper'];
this.changedKnob = 'upper';
}
}
afterChange() {
if (this.changedKnob === 'upper') {
console.log('upper is changed. value : ' + this.upperValue);
} else {
console.log('lower is changed. value : ' + this.lowerValue);
}
}
}

Related

Change value after confirmation in input VUE-JS

I have a table with an input column. This input column will have value if it has been saved in ddbb before.
If this value changes, handled with an event '#blur', I show a modal to confirm the change.
My problem is that if you want to keep the old value it will always change...
I tried to change this value with javascript, but it doesn't work... Any suggestions?
This is my code:
<b-tbody>
<b-tr
v-for="(driver, index) in drivers"
:key="driver.clientId">
<b-td
data-label="Client"
:title="driver.clientName">{{ driver.clientName }}</b-td>
<b-td data-label="Numerator">
<b-input
:id="'numeratorDriver_'+index"
class="driver-numerator text-right"
type="text"
:value="(driver.driverNumerator === undefined) ? 0 : $utils.formatNumber(driver.driverNumerator)"
#blur="calculatePricingDriver($event, index)" /></b-td>
<b-td
class="text-right"
data-label="Pricing"
:title="driver.pricingDriver"><span>{{ driver.pricingDriver }}</span></b-td>
</b-tr>
</b-tbody>
<script>
function calculatePricingCustomDriver (event, index) {
let lastValue = this.drivers[index].driverNumerator
if (this.$store.getters.price !== undefined) {
let title = 'Modify numerator'
let message = 'If you modify numerator driver, the calculated pricing will be deleted'
this.$bvModal.msgBoxConfirm(message, {
title: title,
size: 'sm',
buttonSize: 'sm',
okTitle: 'Yes',
okVariant: 'primary',
cancelTitle: 'No',
cancelVariant: 'primary',
hideHeaderClose: false,
centered: true
})
.then(confirmed => {
if (confirmed) {
this.$http.get('delete-price', { params: { id: this.$store.getters.id } })
.then((response) => {
if (response.status === this.$constants.RESPONSE_STATUS_OK) {
this.price = ''
let newNumerator = event.target.value
this.drivers[index].driverNumerator = Number(newNumerator)
let sumTotal = _.sumBy(this.drivers, 'driverNumerator')
for (let i = 0; i < this.drivers.length; i++) {
this.drivers[i].pricingDriver = (this.drivers[i].driverNumerator / sumTotal).toFixed(2)
}
} else {
this.drivers[index].driverNumerator = lastValue
// this is that I want change because it doesn't work fine
document.getElementById('numeratorDriver_' + index).value = lastValue
}
})
} else {
this.drivers[index].driverNumerator = lastValue
document.getElementById('numeratorDriver_' + index).value = lastValue
}
})
.catch(() => {
/* Reset the value in case of an error */
this.$utils.showModalError()
})
} else {
let newNumerator = event.target.value
this.drivers[index].driverNumerator = Number(newNumerator)
let sumTotal = _.sumBy(this.drivers, 'driverNumerator')
for (let i = 0; i < this.drivers.length; i++) {
this.drivers[i].pricingDriver = (this.drivers[i].driverNumerator / sumTotal).toFixed(2)
}
}
}
</script>
how is calculatePricingCustomDriver being loaded into your Vue component? For it to be called like that from #blur you would need to define it as a method:
<template>
<!-- your table-->
</template>
<script>
export default {
name: "MyComponent",
methods : {
calculatePricingCustomDriver () {
// your code
}
}
}
</script>
Or it could be installed as a global mixin

how to force user input number only in <vue-numeric>

In my <vue-numeric>
<vue-numeric
currency="RMB"
separator=","
v-bind:minus="false"
v-model="amount"
v-bind:precision="2"
class="form-control form-control-lg bg-secondary border-0 text-white"
></vue-numeric>
By using this code it can convert the user input to number type even the input contains string inside, but what I want is user only can insert numberand when alphabet pressed, it will display nothing
You could try adding a keydown event listener and test the keycode to see if it's a number:
#keydown="testNumber"
methods: {
testNumber({ keyCode }) {
if(keyCode < 48 || keyCode > 57) {
event.preventDefault()
}
}
}
What you can do is extend/ overwrite the component options - e.g.:
import VueNumeric from "vue-numeric";
const inputHanlder = VueNumeric.methods["onInputHandler"];
VueNumeric.methods["onInputHandler"] = function(val) {
// here the sanitizer
this.amount = this.amountNumber;
inputHanlder.bind(this)();
};
VueNumeric.watch["valueNumber"] = function(newValue) {
console.log(newValue);
if (this.$refs.numeric !== document.activeElement) {
this.amount = this.format(newValue);
} else {
this.amount = newValue;
}
};
export default VueNumeric;

How to hide onScroll header in ionic 4?

I want to hide the header on scroll in Ionic 4 Beta 5.
I tried all the directives solutions, but none of them work for me.
So, are there any methods that work?
Use below directive
import { IonContent, DomController } from '#ionic/angular';
import { Directive, ElementRef, Input, Renderer2, SimpleChanges } from '#angular/core';
#Directive({
selector: '[scrollHide]'
})
export class ScrollHideDirective {
#Input('scrollHide') config: ScrollHideConfig;
#Input('scrollContent') scrollContent: IonContent;
contentHeight: number;
scrollHeight: number;
lastScrollPosition: number;
lastValue: number = 0;
constructor(private element: ElementRef, private renderer: Renderer2, private domCtrl: DomController) {
}
ngOnChanges(changes: SimpleChanges) {
if(this.scrollContent && this.config) {
this.scrollContent.scrollEvents = true;
let scrollStartFunc = async (ev) => {
const el = await this.scrollContent.getScrollElement();
this.contentHeight = el.offsetHeight;
this.scrollHeight = el.scrollHeight;
if (this.config.maxValue === undefined) {
this.config.maxValue = this.element.nativeElement.offsetHeight;
}
this.lastScrollPosition = el.scrollTop;
};
if(this.scrollContent && this.scrollContent instanceof IonContent) {
this.scrollContent.ionScrollStart.subscribe(scrollStartFunc);
this.scrollContent.ionScroll.subscribe(async (ev) => this.adjustElementOnScroll(ev));
this.scrollContent.ionScrollEnd.subscribe(async (ev) => this.adjustElementOnScroll(ev));
} else if(this.scrollContent instanceof HTMLElement) {
(this.scrollContent as HTMLElement).addEventListener('ionScrollStart', scrollStartFunc);
(this.scrollContent as HTMLElement).addEventListener('ionScroll',async (ev) => this.adjustElementOnScroll(ev));
(this.scrollContent as HTMLElement).addEventListener('ionScrollEnd',async (ev) => this.adjustElementOnScroll(ev));
}
}
}
private adjustElementOnScroll(ev) {
if (ev) {
this.domCtrl.write(async () => {
const el = await this.scrollContent.getScrollElement();
let scrollTop: number = el.scrollTop > 0 ? el.scrollTop : 0;
let scrolldiff: number = scrollTop - this.lastScrollPosition;
this.lastScrollPosition = scrollTop;
let newValue = this.lastValue + scrolldiff;
newValue = Math.max(0, Math.min(newValue, this.config.maxValue));
this.renderer.setStyle(this.element.nativeElement, this.config.cssProperty, `-${newValue}px`);
this.lastValue = newValue;
});
}
}
}
export interface ScrollHideConfig {
cssProperty: string;
maxValue: number;
}
Steps to use:
In your HTML
<ion-header [scrollHide]="headerScrollConfig" [scrollContent]="pageContent">
.
.
.
<ion-content #pageContent>
In your controller: Add config variables
footerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-bottom', maxValue: undefined };
headerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-top', maxValue: 54 };

Vuex loop array of objects and create conditional statement for key:value

i'm am attempting to pull in data through VueX and iterate over an array of objects to get the menu_code. I am successfully get the data i need but i need to show/hide a button according to these conditions:
if ALL of the data in menu_code is NULL, don't show the button.
if one or more of the menu_code data is !== NULL, show the button.
unsure if i'm linking the hasCode data correctly to the button.
// MenuPage.vue
<button v-show="hasMenuCode">hide me if no menu code</button>
<script lang="ts">
import {
Vue,
Component,
Watch,
Prop
} from 'vue-property-decorator';
import {
namespace
} from 'vuex-class';
import MenuItem from "../../models/menu/MenuItem";
export default class MenuPage extends Vue {
#namespace('menu').State('items') items!: MenuItem[];
hasCode = true;
hasMenuCode() {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].menu_code !== null) {
this.hasCode = true;
} else {
this.hasCode = false;
}
}
}
}
</script>
// MenuItem.ts
import AbstractModel from "../AbstractModel";
import Image from '../common/Image';
import MenuRelationship from "./common/MenuRelationship";
import Availability from "../common/Availability";
import Pricing from "../common/Pricing";
interface MenuItemMessages {
product_unavailable_message: string;
}
interface ItemOrderSettings {
min_qty: number;
max_qty: number;
}
export default class MenuItem extends AbstractModel {
name: string;
menu_code: string;
description: string;
image: Image;
has_user_restrictions: boolean;
availability: Availability;
pricing: Pricing;
ordering: ItemOrderSettings;
messages: MenuItemMessages;
prompts: MenuRelationship[];
setMenus: MenuRelationship[];
constructor(props: any) {
super(props);
this.name = props.name;
this.menu_code = props.menu_code;
this.description = props.description;
this.image = props.image;
this.has_user_restrictions = props.has_user_restrictions;
this.availability = new Availability(props.availability);
this.pricing = new Pricing(props.pricing);
this.ordering = props.ordering;
this.messages = props.messages;
this.prompts = props.prompts;
this.setMenus = props.setMenus;
}
}
Base on your requirement, you need to return if there is any item has menu_code, otherwise the loop continues and it only take the value of the final item in this.items
hasMenuCode() {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].menu_code !== null) {
this.hasCode = true;
return
} else {
this.hasCode = false;
}
}
}
Shorter implementation
hasMenuCode() {
return this.items.some(item => item.menu_code !== null)
}

How do you handle a stateless component that will trigger a store roundtrip when calling a callback until it comes back with the new props?

Let's say I have the following component:
class TagsFilter extends React.Component {
static propTypes = {
tags: React.PropTypes.arrayOf(React.PropTypes.string),
onChange: React.PropTypes.func
}
onSelectTag = (tag)=>{
this.props.onChange(this.props.tags.concat([tag]))
}
render () {
return (
<div className='tags-filter'>
<SelectedTags tags={this.props.tags}/>
<TagsSelector tags={this.props.tags} onSelect={this.onSelectTag}/>
</div>
)
}
}
Now, I know that when I call the parent with the new tags it's going to trigger a chain reaction, hit the store, update the list filter there, and cascade the new changes until it comes back again as new props to this component.
The issue is that when it hits the store it triggers server queries and other components updates that make the app stutter for a second, and the update of the TagsFilter component feels sluggish.
So I came up with doing the following:
class TagsFilter extends React.Component {
static propTypes = {
tags: React.PropTypes.arrayOf(React.PropTypes.string),
onChange: React.PropTypes.func
}
componentWillMount () { this.componentWillReceiveProps(this.props) }
componentWillReceiveProps (np) {
this.setState({tags: np})
}
onSelectTag = (tag)=>{
let newTags = this.props.tags.concat([tag])
this.setState({tags: newTags})
setTimeout(()=>{
this.props.onChange({tags: newTags})
}, 50)
}
render () {
return (
<div className='tags-filter'>
<SelectedTags tags={this.state.tags}/>
<TagsSelector tags={this.state.tags} onSelect={this.onSelectTag}/>
</div>
)
}
}
Now, this definitively feels snappier. But I'm not sure if it's the appropriate way to handle these situations. Any insight is appreciated.
Do a comparison in componentWillReceiveProps and only setState if different. You don't need componentWillMount. Here is an example, this is the standard pattern for input.
import React from 'react';
class JInputRender extends React.Component {
render() {
let inputSty = this.props.input.style ? this.props.input.style : {color: 'red'};
let textValue = this.state.textValue;
let colorValue = this.props.input.colorValue ? this.props.input.colorValue : '#1A3212';
let checkedValue = (this.props.input.checkedValue != null) ? this.props.input.checkedValue : false;
let numberValue = this.props.input.numberValue ? this.props.input.numberValue : 0;
let radioValue = this.props.input.radioValue ? this.props.input.radioValue : '';
let radioChecked = (this.props.input.radioChecked != null) ? this.props.input.radioChecked : false;
let min = this.props.input.min ? this.props.input.min : 0;
let max = this.props.input.max ? this.props.input.max : 100;
let step = this.props.input.step ? this.props.input.step : 1;
let inputType = this.props.input.type ? this.props.input.type : 'text';
let returnRadio = (
<input
ref="inputRef"
type={inputType}
style={inputSty}
checked={radioChecked}
value={radioValue}
onChange={this.handleValueChange} />
)
let returnChecked = (
<input
ref="inputRef"
type={inputType}
style={inputSty}
checked={checkedValue}
onChange={this.handleCheckedChange} />
)
let returnColor = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={colorValue}
onChange={this.handleValueChange} />
)
let returnNumber = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={numberValue}
min={min} max={max} step={step}
onChange={this.handleValueChange} />
)
let returnText = (
<input
type={inputType}
ref="inputRef"
style={inputSty}
value={textValue}
onChange={this.handleTextValueChange} />
)
let returnIt = {};
switch (inputType) {
case 'checkbox': returnIt = returnChecked; break;
case 'radio': returnIt = returnRadio; break;
case 'color': returnIt = returnColor; break;
case 'number':
case 'range': returnIt = returnNumber; break;
default: returnIt = returnText; break;
}
return (returnIt);
}
}
export default class JInput extends JInputRender {
constructor() {
super();
this.state = {textValue: ''};
}
componentDidMount = () => {
if (this.props.input.textValue) this.setState({textValue: this.props.input.textValue});
if (this.props.input.focus) this.refs.inputRef.focus();
}
componentWillReceiveProps = (nextProps) => {
if (nextProps.input.textValue && (this.state.textValue != nextProps.input.textValue))
{this.setState({textValue: nextProps.input.textValue});}
}
handleCheckedChange = (event) => { this.props.handleChange(this.props.input.name, event.target.checked); }
handleTextValueChange = (event) => {
let newValue = event.target.value;
this.setState({textValue: newValue});
this.props.handleChange(this.props.input.name, newValue);
}
handleValueChange = (event) => { this.props.handleChange(this.props.input.name, event.target.value); }
}