Setting focus on page load in Ionic - ionic-framework

I can't seem to get focus to set on page load. Here is what I'm trying to do right now:
INPUT:
<ion-input placeholder="SCAN LP TO ACKNOWLEDGE" type="number" [(ngModel)]="this.session.listService.licensePlate" required (keyup.enter)="ValidateAndAcknowledge()" #myInput ></ion-input>
TS FILE
export class AcknowledgePage {
#ViewChild('input') myInput;
constructor() {
}
ionViewDidLoad() {
setTimeout(() => {
console.log(this.myInput);
this.myInput.setFocus();
}, 150);
}
}

So your ViewChild refers 'input' while your template states #myInput. You need to fix that:
export class AcknowledgePage {
#ViewChild('myInput') myInput;
constructor() {
}
ionViewDidLoad() {
setTimeout(() => {
console.log(this.myInput);
this.myInput.setFocus();
}, 150);
}
}
Now even if this code will work for you on Android devices - this may not work on latest iOS (11.3+) since Apple actually wants users to trigger focus via explicit tap action and not programmatic-ally.

I don't know why but #viewChild is not working for me at the end I used document.querySelector() in ionViewDidLoad() and set focus() and It's work for me.

Related

Draftail mention plugin for wagtail

I would like to customise the draftail editor in wagtail in order to have "mentions" functionality (I am using the draft js plugin)
Because my react skills are extremely poor and my knowledge of draftail/draftjs very limited, I am using https://github.com/vixdigital/wagtail-plugin-base as a starting point (without knowing much about what it does) and trying to muddle through the task
I have managed to get it so the mention functionality appears in wagtail. The problem is that it appears in a "sub" editor in the toolbar
enter image description here
How can I avoid creating a sub editor and have it work in the body of the existing editor? Below are the two main JS files in my "wagtail-plugin-base"
index.js
import AutoComplete from './AutoComplete/AutoComplete.js';
window.draftail.registerControl(AutoComplete)
AutoComplete.js
import React, { Component } from '../../node_modules/react';
import createMentionPlugin, { defaultSuggestionsFilter } from '../../node_modules/draft-js-mention-plugin';
import editorStyles from './editorStyles.css';
import { mentions } from './mentions'
import { DraftailEditor, BLOCK_TYPE, INLINE_STYLE, createEditorState } from "draftail"
const mentionPlugin = createMentionPlugin();
const AutoComplete = class SimpleMentionEditor extends Component {
state = {
editorState: createEditorState,
suggestions: mentions,
};
onChange = (editorState) => {
this.setState({
editorState,
});
};
onSearchChange = ({ value }) => {
this.setState({
suggestions: defaultSuggestionsFilter(value, mentions),
});
};
onAddMention = () => {
// get the mention object selected
}
focus = () => {
this.editor.focus();
};
render() {
const { MentionSuggestions } = mentionPlugin
return (
<div>
<DraftailEditor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={[mentionPlugin]}
ref={(element) => { this.editor = element; }}
/>
<MentionSuggestions
onSearchChange={this.onSearchChange}
suggestions={this.state.suggestions}
onAddMention={this.onAddMention}
/>
</div>
);
}
}
export default AutoComplete;
Any pointers much appreciated!

Is it possible to have own custom Context Menu in ag-Grid-community

Can't find the exact answer.
If i decide to opt-in for vanilla JavaScript (non-Angular & Co) ag-Grid-community edition, can i have easy to add my own custom context menu an other custom extensions?
As i seen their docs, context menu is only enterprise level feature.
I seen some treads that there is some caveats, but i personally did not dig deeper.
In general, how easy is to implement self-built features in ag-Grid-community. Or it is better to write own grid?
We have a custom context menu component in our Angular project with ag-grid community, so it's definitely possible.
How it works:
We define all grid columns in templates. If you want a context menu, you put an empty column into the column set and put a special directive on it. The directive accepts a context menu template, which is passed into a custom cellRendererFramework (a menu trigger button, basically). The directive also configures the column to ensure consistent look across grid instances.
This might be not what you've been looking for if you require for menu to open with right mouse click anywhere in a row, but I suppose it shouldn't be that hard to trigger the menu from a different event (check out ag-grid events, there might something suitable).
The snippets below should be straightforward to adapt for your framework of choice. Given you opted into vanilla JS, you'll have to use regular functions to do the same, something like this:
const grid = withContextMenu(new Grid(element, gridOptions), menuOptions).
Here's an example of how we use it:
<ag-grid-angular>
<ag-grid-column headerName='ID' field='id'></ag-grid-column>
<ag-grid-column [contextMenu]='menu'>
<mat-menu #menu='matMenu'>
<ng-template matMenuContent let-item='data'>
<button mat-menu-item (click)='restoreSnapshot(item.id)'>Restore From Snapshot</button>
<a mat-menu-item [routerLink]='[item.id, "remove"]'>Remove</a>
</ng-template>
</mat-menu>
</ag-grid-column>
</ag-grid-angular>
The directive that applies the menu:
const WIDTH = 42;
export const CONTEXT_MENU_COLID = 'context-menu';
#Directive({
selector: '[agGridContextMenu]'
})
export class AgGridContextMenuDirective implements AfterViewInit {
constructor(private gridComponent: AgGridAngular) {}
#Input()
agGridContextMenu!: ElementRef<MatMenu>;
ngAfterViewInit() {
if (!this.agGridContextMenu) return;
setTimeout(() => {
this.gridComponent.api.setColumnDefs([
...this.gridComponent.columnDefs,
{
colId: CONTEXT_MENU_COLID,
cellRendererFramework: CellRendererContextMenuComponent,
width: WIDTH,
maxWidth: WIDTH,
minWidth: WIDTH,
cellStyle: {padding: 0},
pinned: 'right',
resizable: false,
cellRendererParams: {
suppressHide: true,
contextMenu: {
menu: this.agGridContextMenu
}
}
}
]);
});
}
}
The cell renderer component:
#Component({
selector: 'cell-renderer-context-menu',
template: `
<ng-container *ngIf='params.data && params.colDef.cellRendererParams.contextMenu.menu'>
<button
type='button'
mat-icon-button
[matMenuTriggerFor]='params.colDef.cellRendererParams.contextMenu.menu'
[matMenuTriggerData]='{data: params.data}'
>
<mat-icon svgIcon='fas:ellipsis-v'></mat-icon>
</button>
</ng-container>
`,
styleUrls: ['./cell-renderer-context-menu.component.scss']
})
export class CellRendererContextMenuComponent implements ICellRendererAngularComp {
params!: ICellRendererParams;
agInit(params: ICellRendererParams) {
this.params = params;
}
refresh() {
return false;
}
}
A screenshot:
I followed this blogpost, using community edition ag-grid, and it worked! I was surprised because previously I had the experience that cell renderers didn't allow content outside of the cell boundaries to be shown, but somehow popper/tippy is getting around that (I think it adds itself to the top of the DOM with this section of code appendTo: document.body).
https://blog.ag-grid.com/creating-popups-in-ag-grid/
basically, in my javascript CellRenderer:
class MyCellRenderer{
// https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/
init(e){
this.isOpen = false;
this.container = document.createElement("span");
let menubutton = document.createElement("button");
menubutton.innerHTML="&#x1F80B"; //downward arrow
this.tippyInstance = tippy(menubutton);
this.tippyInstance.disable();
this.container.appendChild(menubutton);
menubutton.addEventListener('click', that.togglePopup.bind(this));
}
getGui() {
return this.container;
}
togglePopup() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.configureTippyInstance();
this.eMenu = this.createMenuComponent();
this.tippyInstance.setContent(this.eMenu);
} else {
this.tippyInstance.unmount();
}
}
configureTippyInstance() {
this.tippyInstance.enable();
this.tippyInstance.show();
this.tippyInstance.setProps({
trigger: 'manual',
placement: 'bottom-start',
arrow: false,
interactive: true,
appendTo: document.body,
hideOnClick: true,
onShow: (instance) => {
tippy.hideAll({ exclude: instance });
},
onClickOutside: (instance, event) => {
this.isOpen = false;
instance.unmount();
},
});
}
createMenuComponent() {
let menu = document.createElement('div');
menu.classList.add('menu-container');
let options = {};
options['Delete Row'] = this.menuItemClickHandler.bind(this);
options['Popup an Alert!'] = function(){alert("hello!");};
options['Popup an Alert 2!'] = this.menuItemClickHandler.bind(this);
for (const [key, value] of Object.entries(options)) {
let item = document.createElement('div');
item.classList.add('menu-item');
item.setAttribute('data-action', key.toLowerCase());
item.classList.add('hover_changes_color');
item.innerText = `${key}`; // string formatting example
item.addEventListener('click', value);
menu.appendChild(item);
}
return menu;
}
menuItemClickHandler(event) {
this.togglePopup();
const action = event.target.dataset.action;
if (action === 'delete row') {
this.params.api.applyTransaction({ remove: [this.params.data] });
}
if (action === 'popup an alert 2!') {
alert("2");
}
}
}
and in styles.css:
.hover_changes_color:hover {
background-color: dimgrey;
cursor: pointer;
}

Ag-Grid Link with link in the cell

I am building angular 4 app with ag-grid and I am having an issue with trying to figure out how to put a link in the cell. Can anybody help me with that issue?
Thanks
Please check this demo
cellRenderer: function(params) {
return ''+ params.value+''
}
In this demo, the cell value for the column 'city' is a hyperlink.
I struggled with this the other day and it was bit more complex than I first thought. I ended up with creating a renderer component to which I send in the link and that needed a bit on NgZone magic to work all the way. You can use it in your column definition like this:
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
inRouterLink: '/yourlinkhere',
}
Component where inRouterLink is the link that you send in and params.value is the cell value. That means that you can route to your angular route that could look something like 'yourlink/:id'. You could also simplify this a bit if you don't want a more generic solution by not sending in the link and just hard coding the link in the template and not using the cellRendererParams.
import { Component, NgZone } from '#angular/core';
import { Router } from '#angular/router';
import { AgRendererComponent } from 'ag-grid-angular';
#Component({
template: '<a [routerLink]="[params.inRouterLink,params.value]" (click)="navigate(params.inRouterLink)">{{params.value}}</a>'
})
export class RouterLinkRendererComponent implements AgRendererComponent {
params: any;
constructor(
private ngZone: NgZone,
private router: Router) { }
agInit(params: any): void {
this.params = params;
}
refresh(params: any): boolean {
return false;
}
// This was needed to make the link work correctly
navigate(link) {
this.ngZone.run(() => {
this.router.navigate([link, this.params.value]);
});
}
}
And register it in
#NgModule({
imports: [
AgGridModule.withComponents([
RouterLinkRendererComponent,
])
],
})
UPDATE: I have written a blog post about this: https://medium.com/ag-grid/enhance-your-angular-grid-reports-with-formatted-values-and-links-34fa57ca2952
This is a bit dated, but it may help someone. The solution with typescript on Angular 5 is similar to what C.O.G has suggested.
In the component's typescript file, the column definition can contain a custom cell rendering function.
columnDefs = [
{headerName: 'Client', field: 'clientName' },
{headerName: 'Invoice Number', field: 'invoiceNumber',
cellRenderer: (invNum) =>
`<a href="/invoice/${invNum.value}" >${invNum.value}</a>` },
];
The lambda function is called while rendering the cell. The 'value' of the parameter that gets passed is what you can use to generate custom rendering.
Inspired by #Michael Karén
This is a improved version that is more flexible.
We can set what text to display in link
We can pass more than 2 routerLink parameters
Resolve routerLink according to data
Support target
Display text only if link is not applicable
And more if you wanted to add, just further edit this component
import { Component } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
export interface IRouterLinkRendererComponentOptions {
routerLinkParams?: any[];
linkDescription?: string;
textOnly?: string;
target?: string;
}
#Component({
template: `
<a *ngIf="params.textOnly == null; else textOnlyBlock"
[routerLink]="params.routerLinkParams"
[target]="params.target ? params.target : '_self'"
>
{{ params.linkDescription }}
</a>
<ng-template #textOnlyBlock>
{{ params.textOnly }}
</ng-template>
`
})
export class RouterLinkRendererComponent implements ICellRendererAngularComp {
params: IRouterLinkRendererComponentOptions;
agInit(params: any): void {
this.params = params.routerLinkRendererComponentOptions(params);
}
refresh(params: any): boolean {
return true;
}
}
So that we can dynamically resolve parameters and return text only if wanted in column definition by
{
...
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
routerLinkRendererComponentOptions: (param): IRouterLinkRendererComponentOptions => {
if (param.data.dispatch_adjustment) {
return {
routerLinkParams: ['/adjustments', param.data.dispatch_adjustment.id, 'edit'],
linkDescription: '#' + param.data.dispatch_adjustment.id
};
} else {
return {
textOnly: '-'
};
}
}
},
...
},
Instead of using href in cellRenderer , it's better to use cellrenderer framework as route link works in it.
Another Disadvantage is if you use href then the entire angular application will reload again it changes the navigation state from imperative to popstate. The angular router works on the imperative state.
I had implemented something similar to Michael and Tom, with only [routerLink] and no (click) handler. But recently I started getting the dreaded warning:
Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?
After experimenting for awhile I found this post and added the navigate click handler function, which made the application start working again, however I found that the 'Navigation triggered outside Angular zone' message was still appearing in the logs.
So while the (click)="navigate()" call triggers the navigation inside the ngZone,the [routerLink] call is still being made, which bothered me. I really didn't want two attempts to navigate to happen - in case anything changed with a future API update.
I decided to replace the anchor tag with a span pseudoLink.
.pseudoLink {
color: blue;
text-decoration: underline;
cursor: pointer;
}
#Component({
template: '<span class="pseudoLink" (click)="navigate()">{{mytitle}}</span>'
})
navigate() {
this.ngZone.run(
() => {
console.log("LinkRendererComponent: navigate: (", this.mylink, ")");
this.router.navigate([this.mylink]);
}
);
}
this.mylink is defined in the agInit() method based on parameters passed in via cellRendererParams.
This works well for my main purpose which is to make the cell look like a link. Only thing I lost was the URL path popup in the browser status bar.
Hope this might help someone else.
Using a cell renderer is the correct solution but missing from the top answer is stopping the click event from reaching AgGrid:
cellRenderer: ({value}) => {
const a = document.createElement('a');
a.innerText = a.href = value;
a.target = '_blank';
// Prevent click from reaching AgGrid
a.addEventListener('click', event => { event.stopPropagation() });
return a;
}
If the click bubbles up to AgGrid it will cause row selection changes, etc if those are enabled.
I created a generic component that is usable for any link cell, uses no workarounds, and logs no warnings.
Usage
columnDefs = [
{
colId: 'My Column',
cellRendererFramework: AgGridLinkCellComponent,
cellRendererParams: {
// `text` and `link` both accept either an string expression (same as `field`) or a function that gets ICellRendererParams
text: 'title',
link: (params: ICellRendererParams) => `/my-path/${_.get(params, 'data.id')}`
}
}
]
Register the component in your AppModule:
imports: [
AgGridModule.withComponents([
AgGridLinkCellComponent
])
]
The component itself:
import * as _ from 'lodash';
import {Component} from '#angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {ICellRendererParams} from 'ag-grid-community';
#Component({
selector: 'app-ag-grid-link-cell-component',
template: '<a [routerLink]="link">{{ text }}</a>',
})
export class AgGridLinkCellComponent implements AgRendererComponent {
link: string;
text: string;
constructor() {
}
agInit(params: ICellRendererParams): void {
this.refresh(params);
}
refresh(params: ICellRendererParams): boolean {
const dataParams = params.colDef.cellRendererParams;
this.link = _.isFunction(dataParams.link) ? dataParams.link(params) : _.get(params.data, dataParams.link);
this.text = _.isFunction(dataParams.text) ? dataParams.link(params) : _.get(params.data, dataParams.text);
return false;
}
}
We had this problem, and its not straightforward.
We ended up solving it in a different way as we use AdapTable on top of ag-Grid.
So we created an AdapTable Action Column and in the RenderFunction provided the link. That worked best for us as we didnt always want the Link to appear so we could use the ShouldRender function to decide whether or not we wanted to display link for each row.

Using jQuery autocomplete or Twitter Typeahead with Aurelia

I’m trying to add an input filed with jQuery ui autocomplete or Twitter Typeahead. I can’t make either work. I get “$(...).typeahead is not a function” or “$(...).autocomplete is not a function” error.
I also tried aurelia-widget from https://github.com/drivesoftware/aurelia-widgets, but I also get “$(...).autocomplete is not a function” error.
I would appreciate if someone could tell me what I am doing wrong.
locate.js
import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import { autocomplete } from 'jquery-ui';
#customElement('locate')
export class Locate {
#bindable data;
constructor(element) {
this.element = element;
}
activate() {}
detached(){}
attached(){
$(this.element).autocomplete({
source:['Japan', 'USA', 'Canada', 'Mexico']
});
}
}
locate.html
<template>
<label for="locator-input"></label>
<div id="locator-input-wrapper">
<input id="locator-input" type="text" placeholder="Search">
</div>
</template>
First, you have to be sure about what 'jquery-ui' exports. Does it export something? I believe it exports nothing, instead, it just add some functions to jquery objects. So, you could try this:
import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import 'jquery-ui';
#customElement('locate')
export class Locate {
#bindable data;
constructor(element) {
this.element = element;
}
activate() {}
detached(){}
attached(){
$(this.element).autocomplete({
source:['Japan', 'USA', 'Canada', 'Mexico']
});
}
}
I had the same error but when I retrieved jquery-ui using npm it worked. So instead of "jspm install jquery-ui" (which gave me the error) try:
jspm install npm:jquery-ui
package.json
"jquery-ui": "npm:jquery-ui#^1.10.5",
I had the same problem with jQuery UI datepicker. So i used jquery-ui-dist instead of jquery-ui when doing NPM install.
import "jquery-ui-dist/jquery-ui";
import "jquery-ui-dist/jquery-ui.min.css";
import "jquery-ui-dist/jquery-ui.theme.min.css";
And then:
$(this.element).datepicker()
There are several step involved on this. Please let me note the key points
First you must install the following packages (I am using nodeJS).
npm install -save jquery jquery-ui
(then and if you are coding on typescript the requested types…)
npm install -save #types/jquery #types/jqueryui
I am installing those packages only for coding with typescript and have intellisense working, but really I will not be using them on runtime.
Where the jquery-ui package resides, on node_modules directory, go and create a ../node_modules/jquery-ui/dist directory.
Then download the built zip minimized version from https://jqueryui.com/ and decompress into that dist directory. Those are the files the we will be really using at runtime.
Configure your AMD loader to point to that dist min file creating paths and shims for jquery and jquery-ui. In my case, the AMD loader is requireJS.
require.config(
{
"paths": {
"jquery": '../node_modules/jquery/dist/jquery.min',
"jquery-ui": '../node_modules/jquery-ui/dist/jquery-ui.min'
(code removed for brevity…)
"shim": {
"jquery": {
"exports": '$'
},
"jquery-ui": {
"exports": '$.autocomplete',
"deps": ['jquery' ]
},
(notice that the line "exports": '$.autocomplete' is not required. Since autocomplete, datepicker, etc. widgets, will be loading onto the $ jQuery global variable, I only used this line only as signaler to my AMD loader the it has really loaded something)
Since my AMD loader don’t “parse” css files, I had to add jquery-ui.min.css style sheet manually to my index.html the
<!DOCTYPE html>
<html>
<head lang="en">
(code removed for brevity…)
<link href="./node_modules/jquery-ui/dist/jquery-ui.min.css" rel="stylesheet" />
Create a custom attribute or a custom element (in my opinion for this case the best choice is a custom attribute
i.e. create a class file called: auto-complete.ts (I am coding on typescript, remove types for vainilla javascript).
import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';
import { fireEvent } 'library';
import * as $ from 'jquery';
import 'jquery-ui';
#inject(DOM.Element)
export class AutoCompleteCustomAttribute {
#bindable source: any;
#bindable options = {};
#bindable({ defaultBindingMode: bindingMode.twoWay }) value: JQueryUI.AutocompleteUIParams;
private readonly element: Element;
constructor(element: Element) {
this.element = element;
}
attached() {
$(this.element).autocomplete({
change: (event, ui) => {
if (ui.item == null) {
$(this.element).val('');
$(this.element).focus();
}
},
select: (label, value) => this.value = value,
source: this.source
}).on('change', e => fireEvent(<any>e.target, 'input'));
}
detached() {
$(this.element).autocomplete('destroy');
}
}
Create a shared module where to code shared functionality (or code directly on custom attribute itself, I am going to stick with the shared module option)
i.e. create a class file called: library.ts
export function fireEvent(element: Element, name: string) {
var event = createEvent(name);
element.dispatchEvent(event);
}
export function createEvent(name: string) {
var event = document.createEvent('Event');
event.initEvent(name, true, true);
return event;
}
The usage of this custom attribute on your code is just to attach it to a input text tag as follows:
<input auto-complete="source.bind:countries; value.two-way: country">
where countries (string array) and country (string) are properties on your view model.
Don’t forget to register your custom attribute as a global resource at your Aurelia project's ./src/resources/index.ts or manually adding it on you main.js configure() function as follows:
aurelia.globalResources(["auto-complete"]);
I hope this answer be usefull
Hi again, I am adding an updated code for the custom attribute here below
import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';
import * as $ from 'jquery';
import 'jquery-ui';
import { fireEvent, AutoCompleteSource } from 'libs/library';
#inject(DOM.Element)
export class AutoCompleteCustomAttribute {
#bindable options = {
applyLabel: true,
forceMatch: true
};
#bindable source: AutoCompleteSource[];
#bindable({ defaultBindingMode: bindingMode.twoWay }) value: number;
#bindable({ defaultBindingMode: bindingMode.twoWay }) label: string;
private readonly element: JQuery<HTMLElement>;
constructor(element: Element) {
this.element = $(element);
}
attached() {
this.element
.autocomplete({
source: this.source,
change: (event, ui) => {
if (ui.item == null && this.options.forceMatch) {
this.element.val('');
}
},
select: (event, ui) => {
if (this.options.applyLabel) {
event.preventDefault();
this.element.val(ui.item.label);
}
this.label = ui.item.label;
this.value = ui.item.value;
},
focus: (event, ui) => {
if (this.options.applyLabel) {
event.preventDefault();
this.element.val(ui.item.label);
}
this.label = ui.item.label;
this.value = ui.item.value;
}
}).on('change', e => fireEvent(<any>e.target, 'input'));
}
detached() {
this.element
.autocomplete('destroy');
}
}
This version funcionality allows us to get the label and the value of the source array when dealing with scenarios where label is the text to search and value is a foreing key.
Added functionality to force the typed text to match one of the existing values.
Added funcionality to apply the label instead of value on the input text display.
Custom attribute should be used as follows:
<input type="text" value="${color}" auto-complete="source.bind:colors;value.bind:colorId;label.bind:color">
where colors (array of { "label": string, "value": number }), colorId (number) and color (string) are properties on your view model.
notice also this new type definition added to the library (just simple typescript stuff)
export type AutoCompleteSource = { "label": string, "value": number };

How to add multiple event handlers to same event in React.js

All:
I wonder if it is possible that binding multiple event handlers to same event?
For example:
var LikeToggleButton = React.createClass({
render: function(){
(function toggle(){
this.setState({liked:!like});
}).bind(this);
return (
<div onClick={toggle}>TOGGLE LIKE</div>
);
}
});
Until this point everything seems normal, but I want to add another feature to that button, which is decide by other option:
For example, I have another switch component(could be anything like checkbox or radio button etc.) called "count toggle", which when enabled, the LikeToggleButton's button will be added another onClick handler which is start counting times of button clicked, I know it could be predesignd into the toggle function, but I just wonder if there is a way to append this part to onClick handler?
Thanks
If you want to have multiple callbacks executed when onClick is triggered, you can have them passed from outside, so you'll have access to them in the props object. Then execute them all (note: code not tested):
var LikeToggleButton = React.createClass({
toggle: function() {
this.setState({liked:!like});
},
handleClick: function(e) {
e.preventDefault();
this.toggle();
for (var i=0, l<this.props.callbacks.length; i<l; i++) {
this.props.callbacks[i].call();
}
},
render: function() {
return (
<div onClick={this.handleClick}>TOGGLE LIKE</div>
);
}
});
BUT, if you want to have components connected between them, you should not do that by calling methods inside handlers. Instead you should use an architectural pattern, where Flux is the obvious choice (but there are lots more).
Take a look to Flux, and here you have more choices.
For an extensible way that does't require the component to know about components that use it - save the onClick event before changing it.
This is highlights extracted from the actual working code:
button.jsx
class Button extends React.Component {
constructor(props) {
super(props);
this.state= { callback: false};
}
click(){
//do stuff here
if(this.state.callback) { this.state.callback.call(); }
}
render () {
this.state.callback = this.props.onClick; // save the onClick of previous handler
return (
<button { ...this.props } type={ this.props.type || "button" } onClick={ this.click.bind(this) } className = this.props.className } >
{ this.props.children }
</button>
);
}
}
export default Button;
Then in another component you can use the button and it can have it's own onClick handler:
class ItemButtons extends React.Component {
itemClick () {
//do something here;
}
render () {
const buttons = [
(
<Button onClick={ this.itemClick.bind(this) } className="item-button">
<span>Item-Button</span>
</Button>
)
];
return (<section>{ buttons }</section>);
}
export default ItemButtons;
To group multiple actions on an event
onMouseDown={(e) => { e.stopPropagation(); alert('hello'); }}
Maybe you can set multiple click event handlers on the same one target as described here: https://gist.github.com/xgqfrms-GitHub/a36b56ac3c0b4a7fe948f2defccf95ea#gistcomment-2136607
Code (copied from linke above):
<div style={{ display: 'flex' }}>
<div style={{
width: '270px',
background: '#f0f0f0',
borderRight: "30px solid red",
minHeight: ' 500px',
maxHeight: '700px',
overflowX: 'hidden',
overflowY: 'scroll',
}}
onClick={this.state.ClickHandler}
onClick={this.stateHandleClick}
className="sidebar-btn"
>
<button onClick={this.props.ClickHandler}>props</button>
<button onClick={(e) => this.props.ClickHandler}>props</button>
<button onClick={this.props.ClickHandler}>props</button>
<button onClick={this.state.ClickHandler}>state</button>
//...
</div>