aurelia-ui-toolkits / aurelia-materialize-bridge

Materialize CSS components for Aurelia
http://aurelia-ui-toolkits.github.io/demo-materialize/
MIT License
155 stars 53 forks source link

Unable to access view-model.ref property #532

Closed jasonhjohnson closed 6 years ago

jasonhjohnson commented 6 years ago

Hi,

I'm unable to get the view-model.ref property bind correctly as it's always returning as undefined.

My view:

<md-collection view-model.ref="list">    

My view model:

public list: MdCollection;

public async attached(): Promise<void> {
    console.log(this.list) // returns undefined
}

Any help is appreciated!

Thanks, Jason

MaximBalaganskiy commented 6 years ago

When attached is called it does not mean that all child controls are attached too. Access a property in a task queue or in a changed handler (after making a property observable of course).

On Wed., 3 Oct. 2018, 9:40 pm Jason, notifications@github.com wrote:

Hi,

I'm unable to get the view-model.ref property bind correctly as it's always returning as undefined.

My view:

My view model: public list: MdCollection; public async attached(): Promise { console.log(this.list) // returns undefined } Any help is appreciated! Thanks, Jason — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub , or mute the thread .
jasonhjohnson commented 6 years ago

The same issue exists when I try to log the item from a "submit()" method I have on that view.

MaximBalaganskiy commented 6 years ago

Please have a look at this gist - works fine there https://gist.run/?id=902237522690de2c0fd3e66fbf34a517

On Wed, 3 Oct 2018 at 22:20, Jason notifications@github.com wrote:

The same issue exists when I try to log the item from a "submit()" method I have on that view.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/aurelia-ui-toolkits/aurelia-materialize-bridge/issues/532#issuecomment-426787136, or mute the thread https://github.com/notifications/unsubscribe-auth/ADimvAyNBFxmax8kDeW1aWPxWCAHiVoRks5uhRwlgaJpZM4XGuj1 .

jasonhjohnson commented 6 years ago

Weird I have the exact same thing except my methods are async.

Not sure if it helps but here's a more expanded set of code:

View

<template>
    <require from="./../resources/elements/person-editor.element"></require>

    <div class="container">
        <div class="row">
            <form class="col s12" submit.delegate="submit()" with.bind="model.yourProfile">
                <h2>Setup Roster</h2>

                <ul class="collection with-header" show.bind="validationController.errors.length" style="margin:20px 0">
                    <li class="collection-header">
                        <h4>Please fix the following issues</h4>
                    </li>
                    <li class="collection-item" repeat.for="error of validationController.errors">${error.message}</li>
                </ul>

                <div class="row">
                    <div class="col s6">
                        <p if.bind="!model.setupRoster.people.length">Your roster is currently empty</p>

                        <input type="hidden" value.bind="model.setupRoster.people & validate" />

                        <md-collection view-model.ref="list">                         
                            <md-collection-item repeat.for="person of model.setupRoster.people">                               
                                <span class="accent-text title">${person.firstName} ${person.lastName}</span>                               
                                <div class="secondary-content">
                                    <a class="material-icons" click.delegate="remove($index)" href="#">delete</a>
                                </div>
                            </md-collection-item>
                        </md-collection>

                        <p><a md-button class="modal-trigger" href="#modal1"><i class="material-icons left">add</i> Add Person to Roster</a></p>
                    </div>
                </div>

                <div class="right">
                    <button md-button="large: true" click.delegate="back()" class="back-button">Back</button>
                    <button md-button="large: true" md-waves="color: light;" md-wait-cursor.bind="waiting" type="submit">Next</button>
                </div>
            </form>
        </div>
    </div>

    <person-editor callback.call="add($event)"></person-editor>
</template>

View Model:

import { Router } from 'aurelia-router';
import { autoinject } from "aurelia-framework";
import { ValidationControllerFactory, validateTrigger, ValidationController } from 'aurelia-validation';
import { MaterializeFormValidationRenderer, MdModal, MdCollection } from 'aurelia-materialize-bridge';
import { LogManager, Logger } from 'services/logger.service';
import { ApplicationModel } from 'models/renewal/application.model';
import { PersonService } from 'services/person.service';
import { PersonModel } from 'models/person.model';

@autoinject
export class SetupRosterViewModel {
    private logger: Logger;
    public validationController: ValidationController = null;
    public waiting: boolean = false;
    public list: any;

    constructor(
        private router: Router,
        private model: ApplicationModel,
        private personService: PersonService,
        validationControllerFactory: ValidationControllerFactory) {
        this.logger = LogManager.getLogger('Setup Roster VM');
        this.validationController = validationControllerFactory.createForCurrentScope();
        this.validationController.validateTrigger = validateTrigger.manual;
        this.validationController.addRenderer(new MaterializeFormValidationRenderer());
    }

    public async activate(params: any): Promise<void> {    
        this.logger.info('ClubId:', this.model.selectClub.selectedClub.clubId);
        this.logger.info('SelectedClubId:', this.model.setupRoster.selectedClubId);

        // To determine whether to retrieve the roster let's
        // check the clubId from the selectClub->selectedClub model to see if it
        // matches the selectedClubId we set on setupRoster->selectedClubId 
        if (this.model.setupRoster.selectedClubId < 1 || this.model.selectClub.selectedClub.clubId != this.model.setupRoster.selectedClubId) {         
            return await this.personService.getByRenewalAndClub(this.model.renewal.renewalId, this.model.selectClub.selectedClub.clubId).then(result => {
                this.model.setupRoster.people = result;
                this.logger.info('Available People', this.model.setupRoster.people);
            });
        }            
    }        

    public attached(): void {         
        this.model.setupRoster.selectedClubId = this.model.selectClub.selectedClub.clubId;
    }       

    public async agree(event: any, newPerson: PersonModel): Promise<void> {       
       await this.add(newPerson);
    }

    public async add(newPerson: PersonModel): Promise<void> {       
        let person = new PersonModel();
        person.firstName = newPerson.firstName;
        person.lastName = newPerson.lastName;

        this.model.setupRoster.people.push(person);  

        this.logger.info('List:', this.list);  // This is undefined
    }    

    public async delete(): Promise<void> {
        let selected = this.list.getSelected();
        let names = selected.map(i => i.firstName);
        this.logger.info("selection changed: " + names.join(", "));
    }

    public async remove(index): Promise<void> {       
        this.model.setupRoster.people.splice(index, 1);
    }

    public async submit(): Promise<void> {
        this.waiting = true;

        this.logger.info('Preparing to submit', this.model.setupRoster);

        this.validationController.validate().then(result => {
            this.logger.info('Validation result:', result);

            if (result.valid) {
                this.waiting = false;
                this.router.navigate('step5');
            }
            else {
                this.waiting = false;
            }

        });
    }

    public async back(): Promise<void> {
        this.router.navigate('step3');
    }

    public async deactivate(): Promise<void> {
        // TODO: Add unsaved changes prompt
    }
} 
MaximBalaganskiy commented 6 years ago

You're using .call which does not preserve this AFAIK

jasonhjohnson commented 6 years ago

Here is another example where .call is not involved and the problem exists (note the firstNameElement bind):

View

<template>
    <div id="modal1" md-modal md-modal.ref="modal">
        <div class="modal-content">
            <h4>Add Person to Roster</h4>
            <form class="col s12">
                <div class="row">
                    <div class="col s6">
                        <md-input label="First Name" value.bind="model.firstName & validateOnBlur" validate="true"
                            blur-on-enter="true" view-model.ref="firstNameElement">
                            <i md-prefix class="material-icons">account_circle</i>
                        </md-input>
                    </div>
                    <div class="col s6">
                        <md-input label="Last Name" value.bind="model.lastName & validateOnBlur" validate="true"
                            blur-on-enter="true" keyup.delegate="enterKeyPress($event)">
                        </md-input>
                    </div>
                </div>
            </form>
        </div>
        <div class="modal-footer">
            <a click.delegate="agree($event)" md-button="flat: true;" md-waves="color: accent;" class="modal-action">Add</a>   
            <a click.delegate="disagree()" md-button="flat: true;" md-waves="color: accent;" class="modal-action modal-close">Cancel</a>
        </div>
    </div>
</template>

View Model:

import { bindable, autoinject } from 'aurelia-framework';
import { PersonModel } from 'models/person.model';
import { ValidationControllerFactory, validateTrigger, ValidationController } from 'aurelia-validation';
import { MaterializeFormValidationRenderer, MdModal, MdInput } from 'aurelia-materialize-bridge';

@autoinject
export class PersonEditorCustomElement {
    @bindable callback: any;
    public validationController: ValidationController = null;
    public modal: MdModal;
    public model: PersonModel = new PersonModel();
    public firstNameInput: MdInput;

    constructor(validationControllerFactory: ValidationControllerFactory) {
        this.validationController = validationControllerFactory.createForCurrentScope();
        this.validationController.validateTrigger = validateTrigger.manual;
        this.validationController.addRenderer(new MaterializeFormValidationRenderer());
    }

    public agree(event: any): void {
        console.log(this.firstNameInput); // This is undefined

        this.validationController.validate().then(result => {
            if (result.valid) {             
                this.modal.close();
                this.model = null;
            }
            else {

            }

        });
    }  
}

I was able to get the md-input element by doing: ref="inputElement". However, when I call this.inputElement.focus() it doesn't do anything.

Thanks, Jason

MaximBalaganskiy commented 6 years ago

The binding context of the modal content is not your view model I think. Aurelia chrome extension is very useful in cases like this

On Fri., 5 Oct. 2018, 7:20 pm Jason, notifications@github.com wrote:

Here is another example where .call is not involved and the problem exists (note the firstNameElement bind):

View

View Model:

import { bindable, autoinject } from 'aurelia-framework'; import { PersonModel } from 'models/person.model'; import { ValidationControllerFactory, validateTrigger, ValidationController } from 'aurelia-validation'; import { MaterializeFormValidationRenderer, MdModal, MdInput } from 'aurelia-materialize-bridge';

@autoinject export class PersonEditorCustomElement { @bindable callback: any; public validationController: ValidationController = null; public modal: MdModal; public model: PersonModel = new PersonModel(); public firstNameInput: MdInput;

constructor(validationControllerFactory: ValidationControllerFactory) {
    this.validationController = validationControllerFactory.createForCurrentScope();
    this.validationController.validateTrigger = validateTrigger.manual;
    this.validationController.addRenderer(new MaterializeFormValidationRenderer());
}

public agree(event: any): void {
    console.log(this.firstNameInput); // This is undefined

    this.validationController.validate().then(result => {
        if (result.valid) {
            this.modal.close();
            this.model = null;
        }
        else {

        }

    });
}

}

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/aurelia-ui-toolkits/aurelia-materialize-bridge/issues/532#issuecomment-427438314, or mute the thread https://github.com/notifications/unsubscribe-auth/ADimvCN9Ecp2xtEhliIabWdGcTdT4rqaks5uh5TmgaJpZM4XGuj1 .

MaximBalaganskiy commented 6 years ago

Haha, actually, this one us typo :) fitstNameElement vs firstNameInput

On Fri., 5 Oct. 2018, 7:20 pm Jason, notifications@github.com wrote:

Here is another example where .call is not involved and the problem exists (note the firstNameElement bind):

View

View Model:

import { bindable, autoinject } from 'aurelia-framework'; import { PersonModel } from 'models/person.model'; import { ValidationControllerFactory, validateTrigger, ValidationController } from 'aurelia-validation'; import { MaterializeFormValidationRenderer, MdModal, MdInput } from 'aurelia-materialize-bridge';

@autoinject export class PersonEditorCustomElement { @bindable callback: any; public validationController: ValidationController = null; public modal: MdModal; public model: PersonModel = new PersonModel(); public firstNameInput: MdInput;

constructor(validationControllerFactory: ValidationControllerFactory) {
    this.validationController = validationControllerFactory.createForCurrentScope();
    this.validationController.validateTrigger = validateTrigger.manual;
    this.validationController.addRenderer(new MaterializeFormValidationRenderer());
}

public agree(event: any): void {
    console.log(this.firstNameInput); // This is undefined

    this.validationController.validate().then(result => {
        if (result.valid) {
            this.modal.close();
            this.model = null;
        }
        else {

        }

    });
}

}

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/aurelia-ui-toolkits/aurelia-materialize-bridge/issues/532#issuecomment-427438314, or mute the thread https://github.com/notifications/unsubscribe-auth/ADimvCN9Ecp2xtEhliIabWdGcTdT4rqaks5uh5TmgaJpZM4XGuj1 .