diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 6eaaeb367..de5065205 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -20,7 +20,7 @@ - + diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 7d62c20f4..d1dddb4fb 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -37,6 +37,8 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; import { CoreTabsComponent } from './tabs/tabs'; import { CoreTabComponent } from './tabs/tab'; import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor'; +import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; +import { CoreDynamicComponent } from './dynamic-component/dynamic-component'; @NgModule({ declarations: [ @@ -59,7 +61,9 @@ import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor CoreSitePickerComponent, CoreTabsComponent, CoreTabComponent, - CoreRichTextEditorComponent + CoreRichTextEditorComponent, + CoreNavBarButtonsComponent, + CoreDynamicComponent ], entryComponents: [ CoreContextMenuPopoverComponent, @@ -89,7 +93,9 @@ import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor CoreSitePickerComponent, CoreTabsComponent, CoreTabComponent, - CoreRichTextEditorComponent + CoreRichTextEditorComponent, + CoreNavBarButtonsComponent, + CoreDynamicComponent ] }) export class CoreComponentsModule {} diff --git a/src/components/dynamic-component/dynamic-component.html b/src/components/dynamic-component/dynamic-component.html new file mode 100644 index 000000000..99c89fec9 --- /dev/null +++ b/src/components/dynamic-component/dynamic-component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/dynamic-component/dynamic-component.ts b/src/components/dynamic-component/dynamic-component.ts new file mode 100644 index 000000000..01fa9532d --- /dev/null +++ b/src/components/dynamic-component/dynamic-component.ts @@ -0,0 +1,168 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + Component, Input, ViewChild, OnInit, OnChanges, DoCheck, ViewContainerRef, ComponentFactoryResolver, + KeyValueDiffers, SimpleChange +} from '@angular/core'; +import { CoreLoggerProvider } from '../../providers/logger'; + +/** + * Component to create another component dynamically. + * + * You need to pass the class of the component to this component (the class, not the name), along with the input data. + * + * So you should do something like: + * + * import { MyComponent } from './component'; + * + * ... + * + * this.component = MyComponent; + * + * And in the template: + * + * + *

Cannot render the data.

+ *
+ * + * Please notice that the component that you pass needs to be declared in entryComponents of the module to be created dynamically. + * + * The contents of this component will be displayed if no component is supplied or it cannot be created. In the example above, + * if no component is supplied then the template will show the message "Cannot render the data.". + */ +@Component({ + selector: 'core-dynamic-component', + templateUrl: 'dynamic-component.html' +}) +export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck { + + @Input() component: any; + @Input() data: any; + + // Get the container where to put the dynamic component. + @ViewChild('dynamicComponent', { read: ViewContainerRef }) set dynamicComponent(el: ViewContainerRef) { + this.container = el; + this.createComponent(); + } + + instance: any; + container: ViewContainerRef; + protected logger: any; + protected differ: any; // To detect changes in the data input. + + constructor(logger: CoreLoggerProvider, private factoryResolver: ComponentFactoryResolver, differs: KeyValueDiffers) { + this.logger = logger.getInstance('CoreDynamicComponent'); + this.differ = differs.find([]).create(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.createComponent(); + } + + /** + * Detect changes on input properties. + */ + ngOnChanges(changes: { [name: string]: SimpleChange }): void { + if (!this.instance && changes.component) { + this.createComponent(); + } + } + + /** + * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). + */ + ngDoCheck(): void { + if (this.instance) { + // Check if there's any change in the data object. + const changes = this.differ.diff(this.data); + if (changes) { + this.setInputData(); + if (this.instance.ngOnChanges) { + this.instance.ngOnChanges(this.createChangesForComponent(changes)); + } + } + } + } + + /** + * Create a component, add it to a container and set the input data. + * + * @return {boolean} Whether the component was successfully created. + */ + protected createComponent(): boolean { + if (!this.component || !this.container) { + // No component to instantiate or container doesn't exist right now. + return false; + } + + if (this.instance) { + // Component already instantiated. + return true; + } + + try { + // Create the component and add it to the container. + const factory = this.factoryResolver.resolveComponentFactory(this.component), + componentRef = this.container.createComponent(factory); + + this.instance = componentRef.instance; + + this.setInputData(); + + return true; + } catch (ex) { + this.logger.error('Error creating component', ex); + + return false; + } + } + + /** + * Set the input data for the component. + */ + protected setInputData(): void { + for (const name in this.data) { + this.instance[name] = this.data[name]; + } + } + + /** + * Given the changes on the data input, create the changes object for the component. + * + * @param {any} changes Changes in the data input (detected by KeyValueDiffer). + * @return {{[name: string]: SimpleChange}} List of changes for the component. + */ + protected createChangesForComponent(changes: any): { [name: string]: SimpleChange } { + const newChanges: { [name: string]: SimpleChange } = {}; + + // Added items are considered first change. + changes.forEachAddedItem((item) => { + newChanges[item.key] = new SimpleChange(item.previousValue, item.currentValue, true); + }); + + // Changed or removed items aren't first change. + changes.forEachChangedItem((item) => { + newChanges[item.key] = new SimpleChange(item.previousValue, item.currentValue, false); + }); + changes.forEachRemovedItem((item) => { + newChanges[item.key] = new SimpleChange(item.previousValue, item.currentValue, true); + }); + + return newChanges; + } +} diff --git a/src/components/navbar-buttons/navbar-buttons.scss b/src/components/navbar-buttons/navbar-buttons.scss new file mode 100644 index 000000000..d800187c1 --- /dev/null +++ b/src/components/navbar-buttons/navbar-buttons.scss @@ -0,0 +1,3 @@ +core-navbar-buttons, .core-navbar-button-hidden { + display: none !important; +} diff --git a/src/components/navbar-buttons/navbar-buttons.ts b/src/components/navbar-buttons/navbar-buttons.ts new file mode 100644 index 000000000..98edb3421 --- /dev/null +++ b/src/components/navbar-buttons/navbar-buttons.ts @@ -0,0 +1,148 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit, ContentChildren, ElementRef, QueryList } from '@angular/core'; +import { Button } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '../../providers/utils/dom'; + +/** + * Component to add buttons to the app's header without having to place them inside the header itself. This is meant for + * pages that are loaded inside a sub ion-nav, so they don't have a header. + * + * If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that + * position. If no start/end is specified, then the buttons will be added to the first found in the header. + * + * You can use the [hidden] input to hide all the inner buttons if a certain condition is met. + * + * Example usage: + * + * + * + * + */ +@Component({ + selector: 'core-navbar-buttons', + template: '' +}) +export class CoreNavBarButtonsComponent implements OnInit { + + protected BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden'; + + // If the hidden input is true, hide all buttons. + @Input('hidden') set hidden(value: boolean) { + this._hidden = value; + if (this._buttons) { + this._buttons.forEach((button: Button) => { + this.showHideButton(button); + }); + } + } + + // Get all the buttons inside this directive. + @ContentChildren(Button) set buttons(buttons: QueryList