fix(events): create events module and remove ion-events component

This commit is contained in:
Adam Bradley
2018-03-01 16:06:59 -06:00
parent 3c769a2575
commit e84ddb69dc
8 changed files with 458 additions and 495 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
import { Component, Listen, Method } from '@stencil/core';
const subscriptions = new Map<string, ((event?: any) => void)[]>();
@Component({
tag: 'ion-events'
})
export class Events {
@Method()
subscribe(topic: string, handler: (event?: any) => void) {
const handlers = subscriptions.get(topic) || [];
handlers.push(handler);
subscriptions.set(topic, handlers);
}
@Method()
unsubscribe(topic: string, handler: (event?: any) => void) {
const handlers = subscriptions.get(topic) || [];
const newHandlers = handlers.filter(fun => fun !== handler);
subscriptions.set(topic, newHandlers);
}
@Method()
publish(topic: string, event?: any) {
return publishImpl(topic, event);
}
@Listen('window:online')
@Listen('window:offline')
@Listen('window:orientationchange')
online(event: Event) {
return publishImpl(`app:${event.type}`, event);
}
@Listen('window:statusTap')
statusTap(event: Event) {
return publishImpl(`app:${event.type}`, event);
}
}
// make this method async just to give the browser a chance to chill and do what it needs to do before firing this off
function publishImpl(topic: string, event: any): Promise<any> {
return Promise.resolve().then(() => {
const handlers = subscriptions.get(topic) || [];
for (const handler of handlers) {
handler(event);
}
});
}

View File

@@ -1,22 +0,0 @@
# ion-events
<!-- Auto Generated Below -->
## Methods
#### publish()
#### subscribe()
#### unsubscribe()
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@@ -1,54 +0,0 @@
'use strict';
const { By, until } = require('selenium-webdriver');
const { register, Page, platforms } = require('../../../../../scripts/e2e');
const expect = require('chai').expect;
class E2ETestPage extends Page {
constructor(driver, platform) {
super(driver, `http://localhost:3333/src/components/events/test/basic?ionicplatform=${platform}`);
}
}
platforms.forEach(platform => {
describe('events/basic', () => {
register('should init', driver => {
const page = new E2ETestPage(driver, platform);
return page.navigate('#content');
});
register('subscribers should receive event on or shortly after button click', async (driver, testContext) => {
testContext.timeout(2000);
const page = new E2ETestPage(driver, platform);
await wait(300);
// go to page two
const publishButtonSelector = 'ion-button';
const publishButton = await getElement(driver, publishButtonSelector);
publishButton.click();
await wait(300);
const secretOneElement = await getElement(driver, '.secret-one');
const secretOneText = await secretOneElement.getText();
expect(secretOneText).to.equal('Taco');
const secretTwoElement = await getElement(driver, '.secret-two');
const secretTwoText = await secretTwoElement.getText();
expect(secretTwoText).to.equal('Burrito');
});
});
});
async function getElement(driver, selector) {
driver.wait(until.elementLocated(By.css(selector)));
const element = driver.findElement(By.css(selector));
await driver.wait(until.elementIsVisible(driver.findElement(By.css(selector))));
return element;
}
function wait(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
})
}

View File

@@ -1,62 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Events</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="/dist/ionic.js"></script>
</head>
<body onload="runTest()">
<ion-events></ion-events>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Events</ion-title>
</ion-toolbar>
</ion-header>
<ion-content id="content" padding>
<div class="container">
<div>
The first secret is: <span class="secret-one">TBD</span>
</div>
<div>
The second secret is: <span class="secret-two">TBD</span>
</div>
</div>
<ion-button class="publish">Publish the data</ion-button>
</ion-content>
</ion-app>
<script>
async function runTest() {
const ionEvents = document.querySelector('ion-events');
await ionEvents.componentOnReady();
ionEvents.subscribe('secretOne', (secretOne) => {
const span = document.querySelector('.secret-one');
span.textContent = secretOne;
});
ionEvents.subscribe('secretTwo', (secretTwo) => {
const span = document.querySelector('.secret-two');
span.textContent = secretTwo;
});
const publishButton = document.querySelector('ion-button');
publishButton.addEventListener('click', async () => {
ionEvents.publish('secretOne', 'Taco');
ionEvents.publish('secretTwo', 'Burrito');
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,101 @@
export class Events {
private c: {[topic: string]: Function[]} = [] as any;
/**
* Subscribe to an event topic. Events that get posted to that topic will trigger the provided handler.
*
* @param {string} topic the topic to subscribe to
* @param {function} handler the event handler
*/
subscribe(topic: string, ...handlers: Function[]) {
if (!this.c[topic]) {
this.c[topic] = [];
}
handlers.forEach((handler) => {
this.c[topic].push(handler);
});
}
/**
* Unsubscribe from the given topic. Your handler will no longer receive events published to this topic.
*
* @param {string} topic the topic to unsubscribe from
* @param {function} handler the event handler
*
* @return true if a handler was removed
*/
unsubscribe(topic: string, handler: Function = null) {
let t = this.c[topic];
if (!t) {
// Wasn't found, wasn't removed
return false;
}
if (!handler) {
// Remove all handlers for this topic
delete this.c[topic];
return true;
}
// We need to find and remove a specific handler
let i = t.indexOf(handler);
if (i < 0) {
// Wasn't found, wasn't removed
return false;
}
t.splice(i, 1);
// If the channel is empty now, remove it from the channel map
if (!t.length) {
delete this.c[topic];
}
return true;
}
/**
* Publish an event to the given topic.
*
* @param {string} topic the topic to publish to
* @param {any} eventData the data to send as the event
*/
publish(topic: string, ...args: any[]) {
var t = this.c[topic];
if (!t) {
return null;
}
let responses: any[] = [];
t.forEach((handler: any) => {
responses.push(handler(...args));
});
return responses;
}
}
export function setupEvents(win: Window, doc: Document) {
const events = new Events();
win.addEventListener('online', ev => events.publish('app:online', ev));
win.addEventListener('offline', ev => events.publish('app:offline', ev));
win.addEventListener('orientationchange', ev => events.publish('app:rotated', ev));
win.addEventListener('statusTap', () => {
const centerElm = doc.elementFromPoint(win.innerWidth / 2, win.innerHeight / 2);
if (centerElm) {
const scrollElm = centerElm.closest('ion-scroll');
if (scrollElm) {
scrollElm.componentOnReady(() => scrollElm.scrollToTop(300));
}
}
});
return events;
}

View File

@@ -1,7 +1,8 @@
import 'ionicons';
import { createConfigController } from './config-controller';
import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs';
import { createDomControllerClient } from './dom-controller';
import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs';
import { setupEvents } from './events';
const Ionic = (window as any).Ionic = (window as any).Ionic || {};
@@ -30,6 +31,7 @@ Ionic.config = Context.config = createConfigController(
Context.platforms
);
Ionic.Events = Context.Events = setupEvents(window, document);
// first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by prerendering

View File

@@ -1,187 +0,0 @@
// Components
export * from './components/action-sheet/action-sheet';
export { ActionSheetController } from './components/action-sheet-controller/action-sheet-controller';
export * from './components/alert/alert';
export { AlertController } from './components/alert-controller/alert-controller';
export {
Animation,
AnimationBuilder,
AnimationController,
AnimationOptions
} from './components/animation-controller/animation-interface';
export { App } from './components/app/app';
export { Avatar } from './components/avatar/avatar';
export { BackButton } from './components/back-button/back-button';
export { Backdrop } from './components/backdrop/backdrop';
export { Badge } from './components/badge/badge';
export { Button } from './components/button/button';
export { Buttons } from './components/buttons/buttons';
export { Card } from './components/card/card';
export { CardContent } from './components/card-content/card-content';
export { CardHeader } from './components/card-header/card-header';
export { CardTitle } from './components/card-title/card-title';
export { Checkbox } from './components/checkbox/checkbox';
export { Chip } from './components/chip/chip';
export { ChipButton } from './components/chip-button/chip-button';
export { Col } from './components/col/col';
export { Content } from './components/content/content';
export { Datetime } from './components/datetime/datetime';
export { Fab } from './components/fab/fab';
export { FabList } from './components/fab-list/fab-list';
export { FabButton } from './components/fab-button/fab-button';
export { Footer } from './components/footer/footer';
export { Gesture, GestureCallback, GestureDetail } from './components/gesture/gesture';
export { PanRecognizer } from './components/gesture/recognizers';
export * from './components/gesture-controller/gesture-controller-utils';
export { Grid } from './components/grid/grid';
export { Header } from './components/header/header';
export { InfiniteScroll } from './components/infinite-scroll/infinite-scroll';
export { InfiniteScrollContent } from './components/infinite-scroll-content/infinite-scroll-content';
export { Input } from './components/input/input';
export { Item } from './components/item/item';
export { ItemDivider } from './components/item-divider/item-divider';
export { ItemOption } from './components/item-option/item-option';
export { ItemSliding } from './components/item-sliding/item-sliding';
export { KeyboardController } from './components/keyboard-controller/keyboard-controller';
export * from './components/keyboard-controller/keys';
export { Label } from './components/label/label';
export { List } from './components/list/list';
export { ListHeader } from './components/list-header/list-header';
export * from './components/loading/loading';
export { LoadingController } from './components/loading-controller/loading-controller';
export { Menu } from './components/menu/menu';
export {
MenuController,
MenuOverlayAnimation,
MenuPushAnimation,
MenuRevealAnimation
} from './components/menu-controller/menu-controller';
export * from './components/modal/modal';
export { ModalController } from './components/modal-controller/modal-controller';
export * from './components/nav/nav';
export * from './components/nav/nav-interfaces';
export { ViewController } from './components/nav/view-controller';
export { Note } from './components/note/note';
export { PickerColumnCmp } from './components/picker-column/picker-column';
export * from './components/picker/picker';
export { PickerController } from './components/picker-controller/picker-controller';
export * from './components/popover/popover';
export { PopoverController } from './components/popover-controller/popover-controller';
export { RadioGroup } from './components/radio-group/radio-group';
export { Radio, HTMLIonRadioElementEvent } from './components/radio/radio';
export { Range, RangeEvent } from './components/range/range';
export { RangeKnob } from './components/range-knob/range-knob';
export { ReorderGroup } from './components/reorder-group/reorder-group';
export {
RouterEntry,
RouterEntries,
NavOutlet
} from './components/router/router-utils';
export { Row } from './components/row/row';
export { Reorder } from './components/reorder/reorder';
export { Scroll, ScrollCallback, ScrollDetail } from './components/scroll/scroll';
export { Searchbar } from './components/searchbar/searchbar';
export { Segment } from './components/segment/segment';
export { SegmentButton } from './components/segment-button/segment-button';
export { SelectPopoverOption, SelectPopover } from './components/select-popover/select-popover';
export { Select } from './components/select/select';
export { SelectOption } from './components/select-option/select-option';
export { SkeletonText } from './components/skeleton-text/skeleton-text';
export { Slide } from './components/slide/slide';
export { Slides } from './components/slides/slides';
export * from './components/spinner/spinner-configs';
export { Spinner } from './components/spinner/spinner';
export { SplitPane } from './components/split-pane/split-pane';
export { Tab } from './components/tab/tab';
export { TabButton } from './components/tab-button/tab-button';
export { Tabs } from './components/tabs/tabs';
export { Thumbnail } from './components/thumbnail/thumbnail';
export { ToolbarTitle } from './components/title/title';
export * from './components/toast/toast';
export { ToastController } from './components/toast-controller/toast-controller';
export { Toggle } from './components/toggle/toggle';
export { Toolbar } from './components/toolbar/toolbar';
export { PlatformConfig } from './global/platform-configs';
// export all of the component declarations that are dynamically created
export * from './components';
export { DomController, RafCallback } from './global/dom-controller';
export interface Config {
get: (key: string, fallback?: any) => any;
getBoolean: (key: string, fallback?: boolean) => boolean;
getNumber: (key: string, fallback?: number) => number;
}
export type CssClassMap = { [className: string]: boolean };
export interface BaseInputComponent {
disabled: boolean;
hasFocus: boolean;
value: string;
fireFocus: () => void;
fireBlur: () => void;
}
export interface OverlayDismissEvent extends CustomEvent {
detail: OverlayDismissEventDetail;
}
export interface OverlayDismissEventDetail {
data?: any;
role?: string;
}
export interface FrameworkDelegate {
attachViewToDom(elementOrContainerToMountTo: any, elementOrComponentToMount: any, propsOrDataObj?: any, classesToAdd?: string[], escapeHatch?: any): Promise<FrameworkMountingData>;
removeViewFromDom(elementOrContainerToUnmountFrom: any, elementOrComponentToUnmount: any, escapeHatch?: any): Promise<void>;
}
export interface RouterDelegate {
pushUrlState(urlSegment: string, stateObject?: any, title?: string): Promise<any>;
popUrlState(): Promise<any>;
}
export interface FrameworkMountingData {
element: HTMLElement;
component: any;
data: any;
}
declare global {
namespace JSXElements {
export interface DOMAttributes {
// for ion-menu and ion-split-pane
main?: boolean;
padding?: boolean;
['padding-top']?: boolean;
['padding-bottom']?: boolean;
['padding-left']?: boolean;
['padding-right']?: boolean;
['padding-horizontal']?: boolean;
['padding-vertical']?: boolean;
margin?: boolean;
['margin-top']?: boolean;
['margin-bottom']?: boolean;
['margin-left']?: boolean;
['margin-right']?: boolean;
['margin-horizontal']?: boolean;
['margin-vertical']?: boolean;
['no-padding']?: boolean;
['no-margin']?: boolean;
}
}
}
export interface OverlayController {
getTop(): HTMLElement;
}