0
votes

I am trying to develop a form using Ember.js / Ember Data - here is the version information:

DEBUG: -------------------------------

DEBUG: Ember : 1.7.0

DEBUG: Ember Data : 1.0.0-beta.9

DEBUG: Handlebars : 1.3.0

DEBUG: jQuery : 2.1.1

DEBUG: -------------------------------

My main handlebars template is defined as:

<script type="text/x-handlebars" data-template-name="profiles">
<div class="row">
    <div class="content-header">
        <h1 class="box-title">
            <%= displayName %>
            <div class="pull-right">
                <button class="btn bg-olive btn-sm" style="margin-top:-5px;" {{action "save"}}>Save Profile Changes</button>
                <button class="btn bg-orange btn-sm" style="margin-top:-5px;" {{action "cancel"}}>Cancel Changes</button>
            </div>
        </h1>
    </div>
</div>
<div class="row" style="margin-top:10px;">
    <div class="col-md-3">
        {{view "personalInformation"}}
    </div>
    <div class="col-md-3">
        {{view "contactInformation"}}
    </div>
    <div class="col-md-3">
        {{view "addressInformation"}}
    </div>
    <div class="col-md-3">
        {{view "emergencyContactInformation"}}
    </div>
</div>
</script>

The views are defined separately. The basic personal-information handlebars template view definition:

<script type="text/x-handlebars" data-template-name="personal-information">
<div class="box box-primary">
    <div class="box-header">
        <h3 class="box-title">Personal Information</h3>
    </div>
    <form role="form">
    <div class="box-body">
        <div class="form-group">
            <label for="input-firstName">First Name</label>
            {{input type="text" class="form-control" placeholder="First/Given Name" value=firstName}}
        </div>
        <div class="form-group">
            <label for="input-middleNames">Middle Names</label>
            {{input type="text" class="form-control" placeholder="Middle Name(s)" value=middleNames}}
        </div>
        <div class="form-group">
            <label for="input-lastName">Last Name</label>
            {{input type="text" class="form-control" placeholder="Last/Family Name" value=lastName}}
        </div>
        <div class="form-group">
            <label for="input-suffix">Suffix</label>
            {{input type="text" class="form-control" placeholder="Suffix" value=suffix}}
        </div>
        <div class="form-group">
            <label for="input-dob">Date of Birth</label>
            {{input type="text" class="form-control" placeholder="dd/mm/yyyy" value=formattedDoB}}
        </div>
        <div class="form-group">
            <label for="select-gender">Gender</label>
            {{view Ember.Select class="form-control" content=genders value=gender}}
        </div>
    </div>
    </form>
</div>
</script>

Portal.PersonalInformationView = Ember.View.create({
    'templateName': 'personal-information'
});

The contact-information handlebars template/view definition:

<script type="text/x-handlebars" data-template-name="contact-information">
<div class="box box-primary">
    <div class="box-header">
        <div class="pull-left"><h3 class="box-title">Contact Information</h3></div>
        <div class="pull-right box-tools"><button type="button" class="btn btn-default btm-sm" {{action "addContact"}}>+</button></div>
    </div>
    <form role="form">
    {{#each contacts}}
    <div class="box-body bg-gray" style="margin-bottom:5px;">
        <div class="form-group">
            <div class="pull-left">
                {{view Ember.Select class="form-control" content=contactTypes value=type}}
            </div>
            <div class="pull-right box-tools">
                <a><i id="italic_toggle_contact_{{unbound id}}" class="fa fa-chevron-circle-down fa-2x text-blue"></i></a>
                <a><i class="fa fa-trash-o fa-2x text-orange" {{action "deleteContact" id}}></i></a>
            </div>
            <div>&nbsp;</div>
        </div>
        <div id="div_toggle_contact_{{unbound id}}" class="form-group" style="display:none;">
            <div class="pull-left" style="margin-top:3px;">
                {{view Ember.Select class="form-control" content=countryCodes value=code}}
            </div>
            <div class="pull-left" style="margin-top:3px;">
                {{input type="text" class="form-control" value=number}}
            </div>
            <div>&nbsp;</div>
        </div>
    </div>
    {{/each}}
    </form>
</div>
</script>

Portal.ContactInformationView = Ember.View.create({
    'templateName': 'contact-information',

    'click': function(evt) {
        // Step #1: Stop the event from bubbling...
        evt.preventDefault();
        evt.stopPropagation();

        // Step #2: If this is for collapsing / expanding contacts...
        if(evt.target.id.indexOf('italic_toggle') >= 0) {
            var sourceElem = $(evt.target);
            sourceElem.toggleClass('fa-chevron-circle-down');
            sourceElem.toggleClass('fa-chevron-circle-up');

            var targetElem = $('#' + evt.target.id.replace('italic', 'div'));
            targetElem.slideToggle(600);
        }
    }
});

The address-information handlebars template/view definition:

<script type="text/x-handlebars" data-template-name="address-information">
<div class="box box-primary">
    <div class="box-header">
        <div class="pull-left"><h3 class="box-title">Location Information</h3></div>
        <div class="pull-right box-tools"><button type="button" class="btn btn-default btm-sm" {{action "addAddress"}}>+</button></div>
    </div>
    <form role="form">
    {{#each addresses}}
    <div class="box-body bg-gray" style="margin-bottom:5px;">
        <div class="form-group">
            <div class="pull-left">
                {{view Ember.Select class="form-control" content=addressTypes value=type}}
            </div>
            <div class="pull-right box-tools">
                <a><i id="italic_toggle_address_{{unbound id}}" class="fa fa-chevron-circle-down fa-2x text-blue"></i></a>
                <a><i class="fa fa-trash-o fa-2x text-orange" {{action "deleteAddress" id}}></i></a>
            </div>
            <div>&nbsp;</div>
        </div>
        <div id="div_toggle_address_{{unbound id}}" style="display:none;">
        <div class="form-group" style="margin-top:3px;">
            {{view Ember.Select class="form-control" content=countries value=country}}
            {{view Ember.Select class="form-control" content=states value=state}}
            {{view Ember.Select class="form-control" content=cities value=city}}
            {{input type="text" class="form-control" placeholder="PIN Code" value=pincode}}
        </div>
        <div class="form-group">
            {{input type="text" class="form-control" placeholder="Line #1" value=line1}}
            {{input type="text" class="form-control" placeholder="Line #2" value=line2}}
            {{input type="text" class="form-control" placeholder="Line #3" value=line3}}
            {{input type="text" class="form-control" placeholder="Area" value=area}}
        </div>
        </div>
    </div>
    {{/each}}
    </form>
</div>
</script>

Portal.AddressInformationView = Ember.View.create({
    'templateName': 'address-information',

    'click': function(evt) {
        // Step #1: Stop the event from bubbling...
        evt.preventDefault();
        evt.stopPropagation();

        // Step #2: If this is for collapsing / expanding addresses...
        if(evt.target.id.indexOf('italic_toggle') >= 0) {
            var sourceElem = $(evt.target);
            sourceElem.toggleClass('fa-chevron-circle-down');
            sourceElem.toggleClass('fa-chevron-circle-up');

            var targetElem = $('#' + evt.target.id.replace('italic', 'div'));
            targetElem.slideToggle(600);
        }
    }
});

The emergency-contact-information handlebars template/view definition:

<script type="text/x-handlebars" data-template-name="emergency-contact-information">
<div class="box box-primary">
    <div class="box-header">
        <div class="pull-left"><h3 class="box-title">Emergency Contacts</h3></div>
        <div class="pull-right box-tools"><button type="button" class="btn btn-default btm-sm" {{action "addEmergencyContact"}}>+</button></div>
    </div>
    <form role="form">
    {{#each emergencyContacts}}
    <div class="box-body bg-gray" style="margin-bottom:5px;">
        <div class="form-group">
            <div class="pull-left">
                {{input type="text" class="form-control" placeholder="Contact Name" value=name}}
            </div>
            <div class="pull-right box-tools">
                <a><i id="italic_toggle_emergency_contact_{{unbound id}}" class="fa fa-chevron-circle-down fa-2x text-blue"></i></a>
                <a><i class="fa fa-trash-o fa-2x text-orange" {{action "deleteEmergencyContact" id}}></i></a>
            </div>
            <div>&nbsp;</div>
        </div>
        <div id="div_toggle_emergency_contact_{{unbound id}}" class="form-group" style="display:none;">
            <div class="pull-left" style="margin-top:3px;">
                {{view Ember.Select class="form-control" content=countryCodes value=code}}
            </div>
            <div class="pull-left" style="margin-top:3px;">
                {{input type="text" class="form-control" value=number}}
            </div>
            <div>&nbsp;</div>
        </div>
    </div>
    {{/each}}
    </form>
</div>
</script>

Portal.EmergencyContactInformationView = Ember.View.create({
    'templateName': 'emergency-contact-information',

    'click': function(evt) {
        // Step #1: Stop the event from bubbling...
        evt.preventDefault();
        evt.stopPropagation();

        // Step #2: If this is for collapsing / expanding contacts...
        if(evt.target.id.indexOf('italic_toggle') >= 0) {
            var sourceElem = $(evt.target);
            sourceElem.toggleClass('fa-chevron-circle-down');
            sourceElem.toggleClass('fa-chevron-circle-up');

            var targetElem = $('#' + evt.target.id.replace('italic', 'div'));
            targetElem.slideToggle(600);
        }
    }
});

The route, controller, and model definitions are here:

Portal.ProfilesRoute = Ember.Route.extend({
    'setupController': function(controller, model) {
        controller.set('model', model);
    },

    'model': function(params) {
        return this.store.find('profile', params.userId);
    },

    'actions': {
        'didTransition': function() {
            setTimeout(function() {
                $("#input-dob").inputmask("dd/mm/yyyy", {"placeholder": "dd/mm/yyyy"});
            }, 250);
        }
    }
});

Portal.ProfilesController = Ember.ObjectController.extend({
    'actions': {
        'addContact': function() {
            var user = this.get('model'),
                newid = Portal.generateUUID();

            user.get('contacts').addObject(this.store.createRecord('contact', { 
                'id': newid,
                'dbid': newid
            }));
        },

        'deleteContact': function(id) {
            var user = this.get('model');

            this.store.find('contact', id)
            .then(function(contact) {
                user.get('contacts').removeObject(contact);
                contact.deleteRecord();
            });
        },

        'addAddress': function() {
            var user = this.get('model'),
                newid = Portal.generateUUID();

            user.get('addresses').addObject(this.store.createRecord('address', { 
                'id': newid,
                'dbid': newid
            }));
        },

        'deleteAddress': function(id) {
            var user = this.get('model');

            this.store.find('address', id)
            .then(function(address) {
                user.get('addresses').removeObject(address);
                address.deleteRecord();
            });
        },

        'addEmergencyContact': function() {
            var user = this.get('model'),
                newid = Portal.generateUUID();

            user.get('emergencyContacts').addObject(this.store.createRecord('emergencyContact', { 
                'id': newid,
                'dbid': newid
            }));
        },

        'deleteEmergencyContact': function(id) {
            var user = this.get('model');

            this.store.find('emergencyContact', id)
            .then(function(emergencyContact) {
                user.get('emergencyContacts').removeObject(emergencyContact);
                emergencyContact.deleteRecord();
            });
        },

        'save': function() {
            this.model.save();
        },

        'cancel': function() {
            this.model.get('contacts').content.invoke('rollback');
            this.model.get('addresses').content.invoke('rollback');
            this.model.rollback();
        }
    }
});

Portal.Profile = DS.Model.extend({
    'firstName': DS.attr('string'),
    'middleNames': DS.attr('string'),
    'lastName': DS.attr('string'),
    'suffix': DS.attr('string'),
    'dob': DS.attr('date'),
    'gender': DS.attr('string'),

    'contacts': DS.hasMany('contact'),
    'addresses': DS.hasMany('address'),
    'emergencyContacts': DS.hasMany('emergencyContact'),

    'formattedDoB': function(key, value, prevValue) {
        if(arguments.length <= 1) {
            return moment(this.get('dob')).format('DD/MM/YYYY');
        }

        // Set the new DOB
        var newDoB = moment(value, 'DD/MM/YYYY');
        if(newDoB.isValid()) {
            this.set('dob', newDoB.toDate());
        }
    }.property('dob'),

    'genders': ['Male', 'Female']
});

Portal.Contact = DS.Model.extend({
    'dbid': DS.attr('string'),
    'type': DS.attr('string'),
    'code': DS.attr('string'),
    'number': DS.attr('string'),

    'contactTypes': ['Home Landline', 'Office Landline', 'Other Landline', 'Personal Mobile', 'Office Mobile', 'Other Mobile', 'Other'],
    'countryCodes': ['1', '91']
});

Portal.Address = DS.Model.extend({
    'dbid': DS.attr('string'),
    'type': DS.attr('string'),
    'line1': DS.attr('string'),
    'line2': DS.attr('string'),
    'line3': DS.attr('string'),
    'area': DS.attr('string'),
    'city': DS.attr('string'),
    'state': DS.attr('string'),
    'country': DS.attr('string'),
    'pincode': DS.attr('string'),

    'addressTypes': ['Home', 'Office', 'Other'],

    'countries': ['India', 'USA'],

    'states': function() {
        var countryStates = {
            'India': ['Andhra Pradesh', 'Telangana'],
            'USA': ['Alabama', 'Kentucky']
        };

        return countryStates[this.get('country')];
    }.property('country'),

    'cities': function() {
        var stateCities = {
            'Andhra Pradesh': ['Vijayawada', 'Guntur', 'Tirupati'],
            'Telangana': ['Hyderabad', 'Secunderabad']
        };

        return stateCities[this.get('state')];
    }.property('state')
});

Portal.EmergencyContact = DS.Model.extend({
    'dbid': DS.attr('string'),
    'code': DS.attr('string'),
    'number': DS.attr('string'),

    'countryCodes': ['1', '91']
});

When I transition into this route the first time, it works perfectly well. The moment I transition to the home / other route, and try to transition back to this route, Ember throws an assertion failed error:

Uncaught Error: Assertion Failed: calling set on destroyed object" (ember.js:3722)

What am I doing wrong?

1

1 Answers

1
votes

Here are 2 things that I spotted out from your code.

First thing:

You have your ember route mixed together. And you should be clear about singular/plural name convention in routing.

Portal.ProfilesRoute refers to set of of profiles. Portal.ProfileRoute refers to 1 individual profile.

Because you define both within Portal.ProfilesRoute. First time click Ember will automatically routing assume you are return same model, thus doesn't even call your route. But then when you try to navigate back which is when the model hook actually called your routing, and end up with the error.

second thing kind of related to thing 1:

'setupController': function(controller, model) {
        controller.set('model', model);
    },

    'model': function(params) {
        return this.store.find('profile', params.userId);
    }

either one but not both. for example, use setupController if you want to do something with the model.

'setupController': function(controller, model) {
            //do something to the model if you want to 
            // maybe change some attribute 
             model.set('name','blah blah');
            controller.set('model', model);
        },

otherwise just do

'model': function(params) {
        return this.store.find('profile', params.userId);
    },

but yes, as mentioned in thing 1, this should be in singular route which is ProfileRoute

Hope it helps!