Angular2 Service which create, show and manage it's inner Component? How to implement js alert()? - angular2-template

I tried to find a way for having and manage an angular2 Component in a Service but with no success:
I need to create:
AlertService{
alertConfirm(msg): Promise;
}
alertConfirm will prompt an Confirmation window with 2 buttons (Ok, Cancel) and will return users' choise as a Promise.
In General, the idea is to implement the famous JavaScript alert() method
but with a designed UI window and with also a cancel button.
The method will return a Promise with a response of user's choice: "OK" or "Cancel".
I tried to find a way for holding an "anonymous" component, AlertComponent, in AlertService:
AlertComponent{
showMsgConfirm(msg): Promise;
}
The Promise will be set with a response when user close prompt window or click "OK" or "Cancel".
The question:
How to make "AlertService" to have an inner "AlertComponent" which can be managed by it's "alertOK" method?
I mean, I didn't find a way for "alertConfirm" to call "showMsgConfirm" method and to return it's Promise as a response.
for example, calling from main app component:
this.alertService.alertConfirm("Save changes?").then(res => {
if(res.ok){console.log("Can be saved");
}, err=> { });
Any ideas for this?
Thanks,
Update:2 different ideas for solution, but with no sucess to manage the AlertComponent:
import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef } from '#angular/core';
import { AlertComponent } from './../components/modales/AlertComponent/AlertComponent.component';
#Injectable()
export class AlertService {
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
public createAlertComp(vCref: ViewContainerRef): ComponentRef<any> {
let factory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);
/*
//Option 1:
// vCref is needed cause of that injector..
let injector = ReflectiveInjector.fromResolvedProviders([], vCref.parentInjector);
// create component without adding it directly to the DOM
let comp = factory.create(injector);
// add inputs first !! otherwise component/template crashes ..
comp.instance.model = modelInput;
// all inputs set? add it to the DOM ..
vCref.insert(comp.hostView);
return comp;
*/
//Option 2:
var componentRef: ComponentRef<AlertComponent> = vCref.createComponent(factory);
return null;
}
}

And the answer is... :
The Service:
_counter is used for each modal to have a unique name.
comp.instance.close is a property of inner component for subscribing for EventEmitter.
.
import { Injectable, ViewContainerRef, ReflectiveInjector, ComponentFactoryResolver, ComponentRef, EventEmitter } from '#angular/core';
import { CtmAlertComponent } from './ctmAlert/ctmAlert.component';
#Injectable()
export class AlertCtmService {
private _vcr: ViewContainerRef;
private _counter: number = 0;
constructor(private componentFactoryResolver: ComponentFactoryResolver, public viewRef: ViewContainerRef) {
console.log("AlertCtmService.constructor:");
//TODO: Consider appending to this.viewRef: "#alertCtmServiceContainer" as a Dom elemnt perent container which will hold all AlertModals:
// Maybe by:
// this.viewRef.element.nativeElement.insertAdjacentHTML('beforeend', '<div class="alertCtmServiceContainer"></div>');
this._vcr = this.viewRef;
}
public alertOK(alertMsg: string): EventEmitter<any> {
return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, false);
}
public alertConfirm(alertMsg: string): EventEmitter<any> {
return this.createEventEmitterComponent("CtmAlertComponent", alertMsg, true);
}
private createEventEmitterComponent(componentName: string, alertMsg: string, isConfirm: boolean): EventEmitter<any> {
console.log("AlertCtmService.createEventEmitterComponent:");
switch (componentName) {
case "CtmAlertComponent":
default:
var _component = CtmAlertComponent;
break;
}
let factory = this.componentFactoryResolver.resolveComponentFactory(_component);
// vCref is needed cause of that injector..
let injector = ReflectiveInjector.fromResolvedProviders([], this._vcr.parentInjector);
// create component without adding it directly to the DOM
let comp = factory.create(injector);
// add inputs first !! otherwise component/template crashes ..
comp.instance.close.subscribe(resp => {
console.log("AlertCtmService.createEventEmitterComponent: comp.instance.close.subscribe: resp=" + resp.ok);
comp.destroy();
})
comp.instance.alertBodyMsg = alertMsg;
comp.instance.isConfirm = isConfirm;
comp.instance.nameId = "Modal" +(++this._counter).toString();
// all inputs set? add it to the DOM ..
this._vcr.insert(comp.hostView);
//return null;
return comp.instance.close;
}
public init(vCref: ViewContainerRef): ViewContainerRef {
this._vcr = vCref;
return this._vcr;
}
}
Inner Component:
Using Bootstrap for handling display of Modal in UI: modal('show') \ modal('hide').
.
import { Component, AfterViewInit, Input, ViewChild, ElementRef, Renderer, NgZone, EventEmitter} from '#angular/core';
#Component({
selector: 'ctm-alert',
styles: [``],
templateUrl: '/app/shared/alertCtm/ctmAlert/CtmAlert.component.html',
styleUrls: ['./app/shared/alertCtm/ctmAlert/CtmAlert.component.css'],
providers: []
})
export class CtmAlertComponent implements AfterViewInit {
public ModalIsVisible: boolean;
//private static subscriptions: Object = {};
//enums = Enums;
close = new EventEmitter();
public nameId = "";
private isOk = false;
alertBodyMsg: string = "";
isConfirm = false;
constructor() {
console.log("CtmAlertComponent.constructor:");
}
ngAfterViewInit() {
this.showModal();
var attrId = this.getIdAttr();
$('#' + attrId).on('hidden.bs.modal', function () {
debugger;
console.log('CtmAlertComponent: #licenseModal_XXX.on(hidden.bs.modal)');
this.submitStatus();
}.bind(this) );
}
showModal() {
this.ModalIsVisible = true;
var attrId = '#' +this.getIdAttr();
$(attrId).modal('show');
}
hideModal() {
this.ModalIsVisible = false;
var attrId = '#' + this.getIdAttr();
$(attrId).modal('hide');
}
getIdAttr(): string {
return "ctmAlertModal_" + this.nameId;
}
submitStatus() {
var resp = { ok: (this.isOk == true) };
this.close.emit(resp);
}
submitOk() {
this.isOk = true;
this.hideModal();
}
submitCancel() {
this.isOk = false;
this.hideModal();
}
}
App's Declaration:
unfortunately, we must declare the anonymus component in our main-app module.
We must add a declaration of entryComponents: [CtmAlertComponent],
.
import { CtmAlertComponent } from './shared/alertCtm/ctmAlert/ctmAlert.component';
#NgModule({
imports: [
BrowserModule,
HttpModule,
AppRoutingModule,
...
],
declarations: [
CtmAlertComponent,
AppComponent,
...
],
entryComponents: [CtmAlertComponent],
providers: [
...
],
bootstrap: [AppComponent],
})
export class AppModule { }
enableProdMode();
Modal UI:
this html template is based on bootstrap's UI:
.
<div class="ctmAlertModal modal fade in" [id]="getIdAttr()" role="dialog">
<div class="modal-dialog modal-lg" [ngClass]="{'modal-lg-6': true }">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header" style="">
<div class="pull-right" style="position: relative;">
<span class="fa fa-times-circle" aria-hidden="true" style="color: #949494"></span>
</div>
</div>
<div class="modal-body">
<div class="modal-body-msg">
{{alertBodyMsg}}
</div>
<div class="modal-body-buttons">
<div style="margin: 0 auto;" [style.width]="(isConfirm)? '165px' : '70px' ">
<button type="button" *ngIf="isConfirm" class="btn-submit pull-left btn-cancel" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitCancel()">
<!--<img alt="End-Training" class="centering-me2" src="../../../contents/training_state_stop_white.svg">-->
Cancel
</button>
<button type="button" class="btn-submit pull-right" [ngClass]="{'disabled': false }" [disabled]="false" (click)="submitOk()">
<!--<img alt="Resume-Training" src="../../../contents/training_state_play_white.svg">-->
OK
</button>
</div>
</div>
</div>
</div>
</div>
</div>
.
Usage::
for example:
.
this.alertCtmService.alertOK("Save changes???").subscribe(function (resp) {
console.log("alertCtmService.alertOK.subscribe: resp=" + resp.ok);
this.saveData();
}.bind(this) );
**
An example I built : https://plnkr.co/qc1ZM6
**
sources:
building-angular-2-components-on-the-fly-a-dialog-box-example
angular2-ngmodule

Related

Dynamicly add a Button that is linked to an action

On connect, I want to add some markup that is connected to an action within the same controller. Is this possible? How is it done?
This is what I am trying so far in my tricksmods controller:
addEmbedButton() {
const buttonHTML = '<button type="button" class="trix-button" data-trix-attribute="embed" data-action="click->tricksmods#showembed" title="Embed" tabindex="-1">Embed</button>'
this.buttonGroupBlockTools.insertAdjacentHTML("beforeend", buttonHTML)
}
showembed(){
console.log('showembed')
}
The markup is added, but the showembed action is not fired on click.
I worked this out. I needed to use the toolbarElement of the trix editor to get at my button and dialog.
import { Controller } from "#hotwired/stimulus"
import Trix from 'trix'
import Rails from "#rails/ujs"
export default class extends Controller {
static get targets() {
return [ "field" ]
}
connect() {
this.addEmbedButton()
this.addEmbedDialog()
this.eventListenerForEmbedButton()
this.eventListenerForAddEmbedButton()
}
addEmbedButton() {
const buttonHTML = '<button type="button" class="trix-button tricks-embed" data-trix-attribute="embed" data-trix-action="embed" data-action="click->tricks#showembed" title="Embed" tabindex="-1">Embed</button>'
this.buttonGroupBlockTools.insertAdjacentHTML("beforeend", buttonHTML)
}
addEmbedDialog() {
const dialogHTML = `<div class="trix-dialog trix-dialog--link" data-trix-dialog="embed" data-trix-dialog-attribute="embed" data-tricks-target="embeddialog">
<div class="trix-dialog__link-fields">
<input type="text" name="embed" class="trix-input trix-input--dialog" placeholder="Paste your URL" aria-label="embed code" required="" data-trix-input="" disabled="disabled">
<div class="trix-button-group">
<input type="button" class="trix-button trix-button--dialog" data-trix-custom="add-embed" value="Add">
</div>
</div>
</div>`
this.dialogsElement.insertAdjacentHTML("beforeend", dialogHTML)
}
showembed(e){
console.log('showembed')
const dialog = this.toolbarElement.querySelector('[data-trix-dialog="embed"]')
const embedInput = this.dialogsElement.querySelector('[name="embed"]')
if (event.target.classList.contains("trix-active")) {
event.target.classList.remove("trix-active");
dialog.classList.remove("trix-active");
delete dialog.dataset.trixActive;
embedInput.setAttribute("disabled", "disabled");
} else {
event.target.classList.add("trix-active");
dialog.classList.add("trix-active");
dialog.dataset.trixActive = "";
embedInput.removeAttribute("disabled");
embedInput.focus();
}
}
eventListenerForEmbedButton() {
this.toolbarElement.querySelector('[data-trix-action="embed"]').addEventListener("click", e => {
this.showembed(e)
})
}
eventListenerForAddEmbedButton() {
this.dialogsElement.querySelector('[data-trix-custom="add-embed"]').addEventListener("click", event => {
console.log('embeddy')
const content = this.dialogsElement.querySelector("[name='embed']").value
if (content) {
let _this = this
let formData = new FormData()
formData.append("content", content)
Rails.ajax({
type: 'PATCH',
url: '/admin/embed.json',
data: formData,
success: ({content, sgid}) => {
const attachment = new Trix.Attachment({content, sgid})
_this.element.editor.insertAttachment(attachment)
_this.element.editor.insertLineBreak()
}
})
}
})
}
//////////////// UTILS ////////////////////////////////////////////////////
get buttonGroupBlockTools() {
return this.toolbarElement.querySelector("[data-trix-button-group=block-tools]")
}
get buttonGroupTextTools() {
return this.toolbarElement.querySelector("[data-trix-button-group=text-tools]")
}
get buttonGroupFileTools(){
return this.toolbarElement.querySelector("[data-trix-button-group=file-tools]")
}
get dialogsElement() {
return this.toolbarElement.querySelector("[data-trix-dialogs]")
}
get toolbarElement() {
return this.element.toolbarElement
}
}

Infinite scroll not working in old Nebular site

I have a website which is based on an old version of ngx-admin (Nebular) which i am assuming version 2.1.0.
the new nebular documentation does not seem to apply to my website, so i am trying to add an infinite loop functionality using ngx-infinite-scroll, but the scroll event is not fired.
my example which i try to apply is taken from (https://stackblitz.com/edit/ngx-infinite-scroll?file=src%2Fapp%2Fapp.module.ts)
my component:
//our root app component
import { Component } from "#angular/core";
#Component({
selector: "my-app",
styleUrls: ["./test.component.scss"],
templateUrl: "./test.component.html"
})
export class TestComponent {
array = [];
sum = 100;
throttle = 300;
scrollDistance = 1;
scrollUpDistance = 2;
direction = "";
modalOpen = false;
constructor() {
console.log("constructor!!");
this.appendItems(0, this.sum);
}
addItems(startIndex, endIndex, _method) {
for (let i = 0; i < this.sum; ++i) {
this.array[_method]([i, " ", this.generateWord()].join(""));
}
}
appendItems(startIndex, endIndex) {
this.addItems(startIndex, endIndex, "push");
}
prependItems(startIndex, endIndex) {
this.addItems(startIndex, endIndex, "unshift");
}
onScrollDown(ev) {
console.log("scrolled down!!", ev);
// add another 20 items
const start = this.sum;
this.sum += 20;
this.appendItems(start, this.sum);
this.direction = "down";
}
onUp(ev) {
console.log("scrolled up!", ev);
const start = this.sum;
this.sum += 20;
this.prependItems(start, this.sum);
this.direction = "up";
}
generateWord() {
return "Test Word";
}
toggleModal() {
this.modalOpen = !this.modalOpen;
}
}
my html:
<h1 class="title well">
ngx-infinite-scroll v-{{nisVersion}}
<section>
<small>items: {{sum}}, now triggering scroll: {{direction}}</small>
</section>
<section>
<button class="btn btn-info">Open Infinite Scroll in Modal</button>
</section>
</h1>
<div class="search-results"
infinite-scroll
[infiniteScrollDistance]="scrollDistance"
[infiniteScrollUpDistance]="scrollUpDistance"
[infiniteScrollThrottle]="throttle"
(scrolled)="onScrollDown()"
(scrolledUp)="onUp()" style="height:100%">
<p *ngFor="let i of array">
{{ i }}
</p>
</div>
Any hints how i can get the scroll event to fire?
Well, finally i got it.
the tricky part of ngx-admin was to findout that one needs to add withScroll="false" to nb-layout in the theme file.
then in the component file this is what worked for me:
#HostListener("window:scroll", ["$event"])
onWindowScroll() {
//In chrome and some browser scroll is given to body tag
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
console.log("scrolled");
}
}
How to detect scroll to bottom of html element
maybe this will help someone

ag-grid in angular5 row inline edit

I want to know the best way I can give user inline edit in ag-grid on button click.
see the image below.
As per my requirement, if user clicks on edit icon, then ag-grid row goes in fullrow edit mode (able to do from documentation provided onag-grid.com) and at the same time, icons in 'Action' column changes to save and cancel icons. So, want to know how this can be done in Angular5. I need idea of dynamically changing this last column.
There's quite a few steps here that you'll need to implement.
Step 1: Create a custom renderer component
#Component({
selector: 'some-selector',
template: `
<span *ngIf="!this.isEditing">
<button (click)="doEdit()">Edit</button>
</span>
<span *ngIf=this.isEditing">
<button (click)="doSave()">Save</button>
<button (click)="doCancel()">Cancel</button>
</span>
`
})
export class MyRenderer implements ICellRendererAngularComp {
isEditing = false;
params: any;
agInit(params: any): void {
this.params = params;
}
doEdit() {
// we need to loop thru all rows, find the column with the renderer, and 'cancel the edit mode'.
// otherwise, we will end up with rows that has SAVE buttons appearing but not in edit mode
const renderersInOtherRows = this.params.api.getCellRendererInstances(this.params);
if( renderersInOtherRows && renderersInOtherRows.length > 0 ) {
for ( let i=0; i<renderersInOtherRows.length; i++) {
const wrapper = renderersInOtherRows[i];
if ( wrapper.getFrameworkComponentInstance() instanceof MyRenderer ) {
const foundRenderer = wrapper.getFrameworkComponentInstance() as MyRenderer;
if( foundRenderer.isEditing ) {
foundRenderer.doCancel();
}
}
}
}
this.isEditing = true;
this.params.api.startEditingCell( { rowIndex: this.params.node.rowIndex, colKey: 'some-col-field-name'});
}
doCancel() {
this.isEditing = false;
// restore previous data or reload
}
doSave() {
this.isEditing = false;
// do your saving logic
}
}
Step 2: Load the component
#NgModule({
imports:[
AgGridModule.withComponents([MyRenderer]),
// ...
],
declarations: [MyRenderer],
})
export class MyModule();
Step 3: Use the component
SuppressClickEdit = true, will prevent double/single click edit mode
#Component({
selector: 'my-grid',
template: `
<ag-grid-angular #grid
style="width: 100%; height: 500px;"
class="ag-theme-balham"
[rowData]="this.rowData"
[columnDefs]="this.columnDefs"
[editType]="'fullRow'"
[suppressClickEdit]="true"></ag-grid-angular>
`
})
export class MyGridComponent implements OnInit {
columnDefs = [
// ... other cols
{ headerName: '', cellRendererFramework: MyRenderer }
];
}
I was just looking for something similiar and so I thought I would share what I did to get this working. I am new to Angular so this may not be the best way.
This is in my component.html
<button (click)="onEdit()">edit button</button>
<button (click)="onSaveEdit()" *ngIf="!editClicked">save button</button>
<button (click)="onCancelEdit()" *ngIf="!editClicked">cancel</button>
This is in my component.ts
public params: any;
private editClicked;
constructor() {
this.editClicked = true;
}
agInit(params: any): void{
this.params = params;
}
onEdit() {
this.editClicked = !this.editClicked;
this.params.api.setFocusedCell(this.params.node.rowIndex, 'action');
this.params.api.startEditingCell({
rowIndex: this.params.node.rowIndex,
colKey: 'action'
});
}
onSaveEdit() {
this.params.api.stopEditing();
this.editClicked = !this.editClicked;
}
onCancelEdit() {
this.params.api.stopEditing(true);
this.editClicked = !this.editClicked;
}
Hope this helps steer you in the right direction.

File upload from <input type="file">

Using angular 2 beta, I cannot seem to get an <input type="file"> to work.
Using diagnostic, I can see two-way binding for other types such as text.
<form>
{{diagnostic}}
<div class="form-group">
<label for="fileupload">Upload</label>
<input type="file" class="form-control" [(ngModel)]="model.fileupload">
</div>
</form>
In my TypeScript file, I have the following diagnostic line:
get diagnostic() { return JSON.stringify(this.model); }
Could it be that it is the issue of not being JSON? The value is null.
I cannot really verify the value of the input. Уven though the text next to "Choose file ..." updates, I cannot see differences in the DOM for some reason.
I think that it's not supported. If you have a look at this DefaultValueAccessor directive (see https://github.com/angular/angular/blob/master/modules/angular2/src/common/forms/directives/default_value_accessor.ts#L23). You will see that the value used to update the bound element is $event.target.value.
This doesn't apply in the case of inputs with type file since the file object can be reached $event.srcElement.files instead.
For more details, you can have a look at this plunkr: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info:
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" (change)="onChange($event)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
onChange(event) {
var files = event.srcElement.files;
console.log(files);
}
}
#Component({
selector: 'my-app',
template: `
<div>
<input name="file" type="file" (change)="onChange($event)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
file: File;
onChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
this.file = files[0];
console.log(this.file);
}
doAnythingWithFile() {
}
}
There is a slightly better way to access attached files. You could use template reference variable to get an instance of the input element.
Here is an example based on the first answer:
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" #file (change)="onChange(file.files)"/>
</div>
`,
providers: [ UploadService ]
})
export class AppComponent {
onChange(files) {
console.log(files);
}
}
Here is an example app to demonstrate this in action.
Template reference variables might be useful, e.g. you could access them via #ViewChild directly in the controller.
Another way using template reference variable and ViewChild, as proposed by Frelseren:
import { ViewChild } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<div>
<input type="file" #fileInput/>
</div>
`
})
export class AppComponent {
#ViewChild("fileInput") fileInputVariable: any;
randomMethod() {
const files = this.fileInputVariable.nativeElement.files;
console.log(files);
}
}
Also see https://stackoverflow.com/a/40165524/4361955
Try this small lib, works with Angular 5.0.0
https://www.npmjs.com/package/ng2-file-upload
Quickstart example with ng2-file-upload 1.3.0:
User clicks custom button, which triggers upload dialog from hidden input type="file" , uploading started automatically after selecting single file.
app.module.ts:
import {FileUploadModule} from "ng2-file-upload";
your.component.html:
...
<button mat-button onclick="document.getElementById('myFileInputField').click()" >
Select and upload file
</button>
<input type="file" id="myFileInputField" ng2FileSelect [uploader]="uploader" style="display:none">
...
your.component.ts:
import {FileUploader} from 'ng2-file-upload';
...
uploader: FileUploader;
...
constructor() {
this.uploader = new FileUploader({url: "/your-api/some-endpoint"});
this.uploader.onErrorItem = item => {
console.error("Failed to upload");
this.clearUploadField();
};
this.uploader.onCompleteItem = (item, response) => {
console.info("Successfully uploaded");
this.clearUploadField();
// (Optional) Parsing of response
let responseObject = JSON.parse(response) as MyCustomClass;
};
// Asks uploader to start upload file automatically after selecting file
this.uploader.onAfterAddingFile = fileItem => this.uploader.uploadAll();
}
private clearUploadField(): void {
(<HTMLInputElement>window.document.getElementById('myFileInputField'))
.value = "";
}
Alternative lib, works in Angular 4.2.4, but requires some workarounds to adopt to Angular 5.0.0
https://www.npmjs.com/package/angular2-http-file-upload
If you have a complex form with multiple files and other inputs here is a solution that plays nice with ngModel.
It consists of a file input component that wraps a simple file input and implements the ControlValueAccessor interface to make it consumable by ngModel. The component exposes the FileList object to ngModel.
This solution is based on this article.
The component is used like this:
<file-input name="file" inputId="file" [(ngModel)]="user.photo"></file-input>
<label for="file"> Select file </label>
Here's the component code:
import { Component, Input, forwardRef } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '#angular/forms';
const noop = () => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FileInputComponent),
multi: true
};
#Component({
selector: 'file-input',
templateUrl: './file-input.component.html',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class FileInputComponent {
#Input()
public name:string;
#Input()
public inputId:string;
private innerValue:any;
constructor() { }
get value(): FileList {
return this.innerValue;
};
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: FileList) => void = noop;
set value(v: FileList) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: FileList) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
changeFile(event) {
this.value = event.target.files;
}
}
And here's the component template:
<input type="file" name="{{ name }}" id="{{ inputId }}" multiple="multiple" (change)="changeFile($event)"/>
just try (onclick)="this.value = null"
in your html page add onclick method to remove previous value so user can select same file again.

Cannot get deactivate function to fire in durandal with deeplinking

I am new to durandal and single page apps, I am having issues getting the deactivate and canDeactivate function to fire. I am using some custom code to achieve deep linking, which is probably what is causing my issue.
I followed the example here: https://github.com/evanlarsen/DurandalDeepLinkingExample
please also see Durandal Subrouting (Hottowel)
Any help would be most appreciated.
Here is the viewmodel code I am calling:
define([''], function () {
var vm = {
activate: function () {
alert('In activate!');
},
deactivate: function () {
alert('In deactivate!');
},
canDeactivate: function () {
alert('In candeactivate!');
}
}
return vm;
});
Here is the viewhtml
<div class="container-fixed">
<div>
<header>
<!-- ko compose: {view: 'Main/Users/UsersNav'} -->
<!-- /ko-->
</header>
<section id="content" class="in-users">
<!--ko compose: {
model: inUsers, afterCompose: router.afterCompose,
transition: 'entrance',
activate: true
} -->
<!--/ko-->
</section>
</div>
</div>
Here is the calling code:
define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
var App = {
router: router,
activate: activate,
showPage: showPage,
isPageActive: isPageActive,
inUsers: viewModel.activator(),
};
return App;
var defaultPage = '';
function activate(activationData) {
defaultPage = 'ManageUsers';
App.inUsers(convertSplatToModuleId(activationData.splat));
router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
if (currentItem != newItem) {
return false;
}
else {
App.inUsers(convertSplatToModuleId(data.splat));
return true;
}
};
}
function showPage(name) {
return function () {
router.activate('#/Users/' + name);
//router.navigateTo('#/Users/' + name);
App.inUsers(convertNameToModuleId(name));
};
}
function isPageActive(name) {
var moduleName = convertNameToModuleId(name);
return ko.computed(function () {
return App.inUsers() === moduleName;
});
}
// Internal methods
function convertNameToModuleId(name) {
return 'Main/Users/' + name + '/' + name;
}
function convertSplatToModuleId(splat) {
if (splat && splat.length > 0) {
return convertNameToModuleId(splat[0]);
}
return convertNameToModuleId(defaultPage);
}
});
EDIT: (Main master page)
function activate() {
// my convention
router.autoConvertRouteToModuleId = function (url) {
return 'Main/' + url + '/index';
};
return router.activate('Home');
}
Nav HTML for master:
<div class="btn-group">
HOME
RESOURCES
USERS
</div>
Main master:
<div class="container-fixed">
<div>
<header>
<!-- ko compose: {view: 'Main/masterNav'} -->
<!-- /ko-->
</header>
<section id="content" class="main">
<!--ko compose: {model: router.activeItem,
afterCompose: router.afterCompose,
transition: 'entrance'} -->
<!--/ko-->
</section>
<footer>
<!--ko compose: {view: 'Main/masterFooter'} --><!--/ko-->
</footer>
</div>
</div>
The issue you are running into about not being able to deactivate your sub-routed views is because the viewmodel.activator() observable, that is returned from that method, enforces the activator pattern in durandal. That observable is expecting a amd module and not a string.
Even though the string works fine because the compose binding knows how to load modules based off of the string.. the viewmodel activator doesn't know how to load modules from strings.
So, you need to pass the actually module to the observable.
The example I created before just used a string so it will compose the view.. but the activator pattern doesnt work if there is just a string. So, instead you will have to require all your sub-route amd modules into your calling code and then instead of using the
convertSplatToModuleId method.. create a new method that returns the correct module.
So, something like this:
define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
var App = {
router: router,
activate: activate,
showPage: showPage,
isPageActive: isPageActive,
inUsers: viewModel.activator(),
};
return App;
var defaultPage = '';
function activate(activationData) {
defaultPage = 'ManageUsers';
convertSplatToModuleId(activationData.splat).then(function(activeModule){
App.inUsers(activeModule);
})
router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
if (currentItem != newItem) {
return false;
}
else {
convertSplatToModuleId(data.splat).then(function (module) {
App.inUsers(module);
});
return true;
}
};
}
function showPage(name) {
return function () {
router.activate('#/Users/' + name);
//router.navigateTo('#/Users/' + name);
convertNameToModuleId(name).then(function(module){
App.inUsers(module);
});
};
}
// Internal methods
function convertNameToModuleId(name) {
return system.acquire('Main/Users/' + name + '/' + name);
}
function convertSplatToModuleId(splat) {
if (splat && splat.length > 0) {
return convertNameToModuleId(splat[0]);
}
return convertNameToModuleId(defaultPage);
}
});