I have to change the color of the navbar of one page when scrolling a bit.
Here we have part of my xml file:
<ion-header no-border>
<ion-navbar color="{{ toolbar_color }}">
<ion-title (click)="change()">{{userdata.Name}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content fullscreen class="container" (ionScrollEnd)="scrollHandler($event)">
I tryed first by changing it using a click event and it worked fine.
change() {
if ( this.toolbar_color == "danger" ) {
this.toolbar_color = "light"
} else {
this.toolbar_color = "danger"
}
}
And this is the ionScrollEnd listener, that does not work. The event is fired correctly, but the changes on toolbar_color are not taking any effect on the navbar.
scrollHandler(event) {
if ( event.scrollTop > 100 ) {
console.log("ScrollEvent --> "+JSON.stringify(event));
this.toolbar_color = "light"
// this.toolbar_change = true;
} else {
this.toolbar_color = "danger"
// this.toolbar_change = false;
}
}
How the hell can I do this?
Thank you :)
Add #ViewChild(Content) content: Content in the TS file and subscribe to scroll end event. refer this link for working version. Also see the ionic forum discussion on this issue
import { Component, ViewChild, ChangeDetectorRef } from '#angular/core';
import { NavController, Content } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
#ViewChild(Content) content: Content;
Arr = Array; //Array type captured in a variable
num:number = 1000;
toolbar_color: string;
constructor(public navCtrl: NavController, public ref : ChangeDetectorRef) {
this.toolbar_color="secondary";
}
changeColor(){
this.toolbar_color="primary";
this.ref.detectChanges();
}
ionViewDidLoad() {
//this.content.enableJsScroll();
this.content.ionScrollEnd.subscribe(() => {
this.changeColor();
});
}
}
HTML file
<ion-header>
<ion-navbar [color]="toolbar_color">
<ion-title>Home</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h2>Welcome to Ionic!</h2>
<p>
This starter project comes with simple tabs-based layout for apps
that are going to primarily use a Tabbed UI.
</p>
<p>
Take a look at the <code>pages/</code> directory to add or change tabs,
update any existing page or create new pages.
</p>
<div *ngFor="let i of Arr(num).fill(1)">{{i}}</div>
</ion-content>
Update-1
Added code to change color on scrolling
Sometimes angular will not run changeDetector automatically. we can manually trigger it by using ChangeDetectorRef. it's added to detect the changes while scrolling.
Working version is also updated. Please check the above link
Related
I'm using Ionic and Firestore for my web appllication. In a component I show a list of items from firestore database,the detail of an item in url tabs/items/list-detail/ and other button to modify images, then there is a button to return the url tabs/items/. Afterwards, if I return to the tabs/items/list-detail page I would like the list to be reloaded with the modified items, but the page remains the same.
I have tried using ViewWillEnter but doesn't work.
In html page of items there is a button to navigate to detail page:
<ion-button id="detail" *ngIf="registeredAndUpl?.registered!=true" [routerLink]="['/tabs/items/list-detail',id]">View</ion-button>
This is the component list-detail Page:
export class DetailPage implements OnInit, ViewWillEnter {
items: any
userId: any
item0: any
item1: any
constructor(private route: ActivatedRoute, private router: Router,
public authService: AuthenticationService,
) {}
ngOnInit() {
}
ionViewWillEnter() {
this.myDefaultMethodToFetchData();
}
myDefaultMethodToFetchData() {
console.log("IN")
this.getItems().then((data) => {
console.log("IN2222")
this.items = data
this.item0 = this.items[0];
this.item1 = this.items[1];
})
this.userId = this.authService.userData.uid;
}
returnItems(){
this.router.navigate(['/tabs/items/']);
}
getItems() {
const itemsRef = firebase.firestore().collection("user_orders/" + this.userId+"/reservations")
.where("ordini", "<", 10).limit(5);
return itemsRef.get()
.then((querySnapshot) => {
return querySnapshot.docs.map(doc => doc.data());
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}
Then, in html page I have a button to return the items:
<ion-content>
<div class="flexbox-container" style="display:flex;">
<div *ngIf="item0" class="sidebar" style="flex:1;">
<video id="video1" height="320" width="240" controls>
<source src="{{item0?.downloadURL}}" type="video/mp4">
<source src="{{item0?.downloadURL}}" type="video/ogg">
</video>
</div>
<div *ngIf="item1" class="main" style="flex:1;">
<video id="video2" height="320" width="240" controls>
<source src="{{item1?.downloadURL}}" type="video/mp4">
<source src="{{item1?.downloadURL}}" type="video/ogg">
</video>
</div>
</div>
</ion-content>
<ion-button id="button1" (click)="returnItems()">Return</ion-button>
What am I doing wrong?
I've noticed that every time I switch from items to list detail page, using the ionViewWillEnter() method, and try to print something in console, the print is recalculated but the data remain the same, so the problem I think is in html page:
ionViewWillEnter should work. Try ionViewDidEnter.
Maybe is late for an answer in this question but i think will be useful for future users.
Related to OP question the mos efficient way is that using Events. It is something similar of use of custom events in javascript.
Using events you can do or refresh everything even in cached pages/components.
Below shows how you can subscribe a listener then call the event from everywhere, doing that listener to intercept the event you have raised.
Page that needs to be refreshed after back button is pressed
page.ts
constructor(
private events: Events
) {}
ngOnInit() {
this.events.subscribe('back_refresh', (data) => { /*Do your operations here as it's always called */ });
}
Page where back button is present
page.html
<ion-back-button (click)="this.goBack()"></ion-back-button>
page.ts
constructor(
private navController: NavController,
private events: Events
) {}
private goBack(): void{
this.navController.pop();
// also you can pass your data here
this.events.publish('back_refresh', null);
}
in Ionic I managed to make a program with the tts plugin that reproduces the text that I write in an input, but so far I have not been able to implement a button that stops the audio that is played, I would appreciate a lot if you help me.
How do I make a class that stops the audio?
This is my .ts file:
import { Component, OnInit } from '#angular/core';
import { TextToSpeech } from '#ionic-native/text-to-speech/ngx';
#Component({
selector: 'app-textvoice',
templateUrl: './textvoice.page.html',
styleUrls: ['./textvoice.page.scss'],
})
export class TextvoicePage {
text: string;
locale: string;
copyText: string="";
pasteText: string="";
constructor ( private tts: TextToSpeech, public clipboard: Clipboard) {
this.text= '';
this.locale= 'es-MX';
}
playText (){
this.tts.speak({
text: this.text,
locale: this.locale
})
.then((res) => console.log (res))
.catch((err) => console.log (err));
}
stop(){
}
This is my html:
<ion-content>
<ion-button fill="solid" color="light" expand="block" (click)="playText()"><ion-icon name="play-outline"></ion-icon>
A texto
</ion-button>
<ion-button fill="solid" color="light" expand="block" (click)="stop()"><ion-icon name="stop-outline"></ion-icon>
Detener audio
</ion-button>
</ion-content>
You should refer to the documentation for #ionic-native/TextToSpeech as it clearly outlines that stop() is used for this exact purpose.
In your specific scenario, your instance of TextToSpeech is available in the this.tts variable. As such you can invoke stop() by simply calling it on this object within your class's method:
stop(){
this.tts.stop();
}
I already managed to solve, I put this code and when giving it stop if the code worked
stop(){
this.tts.speak("")
}
I have an Angular 7 app, using CDK Drag-n-Drop to drag and drop rows in a very long list.
What should I do to allow the long list to auto scroll when the dragged item out of the current view?
Any sample code I can refer to?
I have faced the same issue, It happens anytime an outside element is scrollable. This is the open issue - https://github.com/angular/components/issues/16677. - I have slightly modified the solution mentioned in this link.
import { Directive, Input, ElementRef, AfterViewInit } from '#angular/core';
import { CdkDrag } from '#angular/cdk/drag-drop';
#Directive({
selector: '[cdkDrag],[actualContainer]',
})
export class CdkDropListActualContainerDirective {
#Input('actualContainer') actualContainer: string;
originalElement: ElementRef<HTMLElement>;
constructor(cdkDrag: CdkDrag) {
cdkDrag._dragRef.beforeStarted.subscribe( () => {
var cdkDropList = cdkDrag.dropContainer;
if (!this.originalElement) {
this.originalElement = cdkDropList.element;
}
if ( this.actualContainer ) {
const element = this.originalElement.nativeElement.closest(this.actualContainer) as HTMLElement;
cdkDropList._dropListRef.element = element;
cdkDropList.element = new ElementRef<HTMLElement>(element);
} else {
cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
cdkDropList.element = this.originalElement;
}
});
}
}
Template
<div mat-dialog-content class="column-list">
<div class="column-selector__list">
<div cdkDropList (cdkDropListDropped)="drop($event)">
<div
*ngFor="let column of data"
cdkDrag
actualContainer="div.column-list"
>
</div>
</div>
</div>
</div>
As mentioned here you just need to add cdkScrollable to your list container.
I have an app with ion-range UI element that is bound to a var using [(ngModel)] (2way) and then i have (ionChange) event that triggers a function that uses the value of ion-range.
Sometimes I need to "set" the range to a specific position by directly changing the var which is bound to ngModel, but I need to avoid triggering ionChange during this operation.
if I do that straightforward (see changeValue method) - the ionChange event will fire the moment the data changes and I need to avoid that (I need to place knob into a new location without triggering the event)
I used a trick with a flag (see changeValueViaFlag method) - now it works as expected, note I also have to use timeout, cause if I don't delay setting flag back to its "normal" state - angular's change detection won't pick up;(
UPDATE:
I tried the trick with ngModelChange which can be found in SO here: ionChange - detect changes only from view to model in Ionic 2 - the problem is with ngModelChange - I am not getting the same "change detection" as I get with ionChange...
So in my app the range is used to select color intensity. If I switch to ngModelChange - shape color intensity is not happening...;/
Question: is there a better way to achieve what I want?
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private testRange: number = 20;
private flag: boolean = true;
constructor(
public navCtrl: NavController,
) {
}
logRange($event) {
if (this.flag) {
console.log("ionChange emitted: ",$event.value, this.testRange)
}
}
changeValue() {
this.testRange = 10;
}
changeValueViaFlag() {
this.flag = false;
this.testRange = 10;
setTimeout(()=>{
this.flag = true;
}, 500)
}
}
<ion-header>
<ion-navbar>
<ion-title>
Ion range test case
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-range [(ngModel)]="testRange" (ionChange)="logRange($event)">
</ion-range>
<button ion-button (click)="changeValue()">Change range value to 10</button>
</ion-content>
click is a DOM event, not related to the higher level ngModel. The proper event for this is ngModelChange. If you want to use DOM events, then forget about ngModel and use $event inside the handler or a #ref.
So, instead of
<input [(ngModel)]="item.completed" (click)="completed(i)" type="checkbox">
it should be
<input [(ngModel)]="item.completed" (ngModelChange)="completed(i)" type="checkbox">
I'm trying to wrap Semantic UI Modal component using portal approach described here
Here is my take at it http://jsfiddle.net/mike_123/2wvfjpy9/
I'm running into issue though, when obtaining a DOM reference and Rendering new markup into it there seem to be old reference still maintained.
render: function() {
return <div className="ui modal"/>; <-- the idea at first was to only return <div/>
},
...
React.render(<div > <----------- originally this element had className="ui modal", but this.node doesn't seem to overtake the original node reference
<i className="close icon"></i>
<div className="header">test</div>
<div className="content">
{props.children}
</div>
</div>, <-----------
this.node);
Any pointers how fix this test case http://jsfiddle.net/mike_123/2wvfjpy9/
You will lose correct vertical positioning and probably animations with approaches mentioned above.
Instead, you can just place your modal's component inside your app's root component and call .modal() with detachable: false. With this option, semantic wouldn't make any DOM manipulations and you won't lose your React DOM event listeners.
Example using Webpack/Babel:
import React, { Component } from 'react'
import $ from 'jquery'
if (typeof window !== 'undefined') {
window.jQuery = $
require('semantic-ui/dist/semantic.js')
}
class App extends Component {
state = {
showModal: false
}
_toggleModal = (e) => {
e.preventDefault()
this.toggleModalState()
}
toggleModalState = () => {
this.setState({ showModal: !this.state.showModal })
}
render() {
return (
<div>
<a href="" onClick={this._toggleModal}></a>
{this.state.showModal
? <Modal toggleModalState={this.toggleModalState}/>
: ''}
</div>
)
}
}
class Modal extends Component {
componentDidMount() {
$(this.modal)
.modal({ detachable: false })
.modal('show')
}
componentWillUnmount() {
$(this.modal).modal('hide')
}
_close = (e) {
e.preventDefault()
alert("Clicked")
this.props.toggleModalState()
}
render() {
return (
<div ref={(n) => this.modal = n} className="ui modal">
<div class="content">
<a onClick={this._close} href="">Click Me</a>
</div>
</div>
)
}
}
When you call this.$modal.modal('show'), it will actually restructure your DOM, and React will not be happy about it. Plus, if you try to put control in your modal, the control will not work.
What you should do is to React.render an already shown modal, i.e. a modal with markup as if $('.ui.modal').modal('show') has been called.
Here is my attempt using "React-Portal" to help with rendering a react component at body level. You can still use your method if you prefer.
// modal.jsx
import React, { Component } from 'react';
import Portal from 'react-portal';
class InnerModal extends Component {
constructor(props) {
super(props);
this.state = { modalHeight: 0 };
}
componentDidMount() {
let modalHeight = window.$('#reactInnerModal').outerHeight();
this.setState({modalHeight: modalHeight});
}
render() {
return (
<div id='reactInnerModal' className='ui standard test modal transition visible active' style={{'margin-top': - this.state.modalHeight / 2}}>
<i className='close icon' onClick={this.props.closePortal}></i>
{this.props.children}
</div>
);
}
}
class Modal extends Component {
render() {
let triggerButton = <button className='ui button'>Open Modal</button>;
return (
<Portal className='ui dimmer modals visible active page transition' openByClickOn={triggerButton} closeOnEsc={true} closeOnOutsideClick={true}>
<InnerModal>
{this.props.children}
</InnerModal>
</Portal>
);
}
}
export default Modal;
Notice that my modal has already been rendered in the markup.
You can then consume the modal as below:
// index.jsx
import React, { Component } from 'react';
import Modal from './modal';
class ModalDemo extends Component {
render() {
return (
<Modal>
<div className='header'>
Profile Picture
</div>
<div className='image content'>
<div className='ui medium image'>
<img src='http://semantic-ui.com/images/avatar2/large/rachel.png' />
</div>
<div className='description'>
<div className="ui header">We've auto-chosen a profile image for you.</div>
<p>We've grabbed the following image from the <a href='https://www.gravatar.com' target='_blank'>gravatar</a> image associated with your registered e-mail address.</p>
<p>Is it okay to use this photo?</p>
</div>
</div>
<div className='actions'>
<div className='ui black deny button'>
Nope
</div>
<div className='ui positive right labeled icon button'>
Yep, that's me
<i className='checkmark icon'></i>
</div>
</div>
</Modal>
);
}
}
React.render(<ModalDemo />, document.getElementById('content'));
With this you don't have to hack your way into DOM manipulation with jQuery, and the control in the modal (button, link, etc, to invoke functions) still works.
Hope this help!
Khanetor answered this question thoroughly and correctly, I just want to contribute one additional tidbit about how to position the Modal. It would be best as a comment, but unfortunately, I don't have the reputation to do so.
Anyways, the first child element of the Portal element needs to be positioned absolutely in order to make the dimmer and resulting modal sit on top of the page content rather than get put beneath it.
First, add style={position:'absolute'} to the Portal declaration in Modal's render method so the dimmer gets set at the top of the page. You end up with:
<Portal className='ui dimmer modals visible active page transition' openByClickOn={triggerButton} closeOnEsc={true} closeOnOutsideClick={true} style={position:'absolute'}>
<InnerModal>
{this.props.children}
</InnerModal>
</Portal>
Next, set the InnerModal's position to relative and decide on a distance from the top of the screen. I used an eighth (or 0.125) of the browser's viewport and got:
class InnerModal extends Component {
constructor(props){
super(props);
this.state = {
modalId : _.uniqueId('modal_'),
style: {}
}
}
componentDidMount() {
this.setState({
style : {
position : 'relative',
top : $(window).height() * 0.125 + 'px'
}
});
}
render(){
return (
<div id={this.state.modalId} className='ui standard modal transition visible active'
style={this.state.style}>
<i className='close icon' onClick={this.props.closePortal}></i>
{ this.props.children }
</div>
);
}
}
With those edits made, I've finally got some working modals in React! Hope this is helpful to someone else running into some of the same issues I've been.