2
votes

I am working on an app for world time. I have an ember model: Clock
I would like to update the time property of clock every second.

For now, I have defined a start method in clock that is responsible for updating a time property of the clock model.

I have two questions regarding this: 1. How can I call the start method of the model from my view 2. Is there any better approach I can take?

Here is my models, views, controllers, routers and templates (incase you are interested):

Model:

App.Clock = DS.Model.extend({
    city: DS.attr('string'),
    country: DS.attr('string'),
    latitude: DS.attr('string'),
    longitude: DS.attr('string'),
    time: DS.attr('date'),
    order: DS.attr('number'),

    start: function() {
        var clock = this;

        var updateTime = function() {
            var timezoneService = Utilities.TimezoneService;

            timezoneService.getTimezone(clock.get('latitude'), clock.get('longitude'), function(timezone) {
                clock.set('time', timezoneService.getDateTime(timezone.offset));
            });
        };

        updateTime();

        window.setInterval(function() {
            updateTime();
        }, 1000);
    }
});

Views:

App.ClocksView = Ember.View.extend({
  templateName : 'world_clock/clocks'
});

App.ClocksNewView = Ember.View.extend({
  templateName : 'world_clock/new'
});

Controllers:

App.ClocksController = Ember.ArrayController.extend({
    sortProperties: ['order']
});

App.ClocksNewController = Ember.ObjectController.extend({
    city: null,
    save: function() {
        var cityName = this.get('city');
        if(!cityName.trim()){
            return;
        }

        var locationService = Utilities.LocationService.getInstance();

        locationService.lookupLocation(cityName, function(location) {
            var city = location.city;
            var country = location.country;
            var lat = location.latitude;
            var lon = location.longitude;

            if(city && country && lat && lon) {
                var clks = App.Clock.find({ city: city });
                clks.on('didLoad', function() {
                    if(clks.get('length') === 0) {
                        var clock = App.Clock.createRecord({
                            city: location.city,
                            country: location.country,
                            latitude: location.latitude,
                            longitude: location.longitude,
                            order: 10
                        });

                        clock.save();
                    }
                });
            }
        });

        this.set('city', '');

        this.get('target').transitionTo('clocks');
    }
});

Routers:

App.Router.map(function() {
    this.resource('clocks', { path: '/' }, function() {
        this.route('new');
    });
});

App.ClocksRoute = Ember.Route.extend({
    model: function() {
        var locationService = Utilities.LocationService.getInstance();

        locationService.getLocation(function(location) {
            App.Clock.createRecord({
                city: location.city,
                country: location.country,
                latitude: location.latitude,
                longitude: location.longitude,
                order: -10
            });
        });

        return App.Clock.find();
    }
});

clocks.handlebars:

{{outlet}}
{{#linkTo "clocks.new"}}Add Clock{{/linkTo}}
<ul>
    {{#each controller}}
    <li>{{city}}, {{country}} - {{time}}</li>
    {{else}}
    You have not defined any clock yet.
    {{/each}}
</ul>

new.handlebars:

<form {{action "save" on="submit"}}>
    {{view Ember.TextField valueBinding="city" placeholder="City"}}
    <button>Save</button>
</form>
1

1 Answers

3
votes

How can I call the start method of the model from my view

Except in rare cases it is not a good idea to call a model method from the view. That said it is possible. For example you might use something like didInsertElement:

{{each controller itemViewClass="App.ClockView" emptyViewClass="App.NoClocksView"}}

App.ClockView = Ember.View.extend({
  template: Ember.Handlebars.compile("<li>{{city}}, {{country}} - {{time}}</li>"),
  didInsertElement: function() {
    var clock = this.get('content');
    clock.start();
  }
});

App.NoClocksView = Ember.View.extend({
  template: Ember.Handlebars.compile("You have not defined any clock yet.")
});

Problem with this approach is that it kinda breaks encapsulation, meaning lots of subtle things might go wrong. For example what if ember re-uses the view so it has a new clock but didInsertElement is not run. That's why Initializing State in didInsertElement is not a good idea. Or what if user navigates to/from this route, so models clocks are left running in background then when user comes back they all start again. Which leads me to:

Is there any better approach I can take?

Probably time should not be a property of App.Clock. It's not like you want to be saving time in the database, right? One alternative is: - give App.Clock a computedProperty for timezone offset - add a currentTime property to clocksController - when ClocksView has been inserted, use setTimeout to refresh currentTime every second - make a component (world-clock) which renders a specific clock - add computed property time to world-clock which calculates time given the currentTime and the clock's timezone offset.

So something like this:

<script type="text/x-handlebars" id="clocks">
{{outlet}}
{{#linkTo "clocks.new"}}Add Clock{{/linkTo}}
<ul>
{{#each clock in controller}}
<li>
{{world-clock city="clock.city" country="clock.country" offset="clock.offset" currentTime="currentTime"}}
</li>  
{{/each}}
</ul>
</script>

<script type="text/x-handlebars" id="components/world-clock">
  {{city}}, {{country}} - {{time}}
</script>

App.Clock = DS.Model.extend({
  city: DS.attr('string'),
  country: DS.attr('string'),
  latitude: DS.attr('string'),
  longitude: DS.attr('string'),
  order: DS.attr('number'),
  offset: function() {
    var offset;
    var service = Utilities.TimezoneService;
    service.getTimezone(this.get('latitude'), this.get('longitude'), function(zone) {
      offset = timezone.offset
    });
    return offset;
  }.property('latitude', 'longitude')
});

App.WorldClockComponent = Ember.Component.extend({
  tagName: 'li',
  time: function() {
    var service = Utilities.TimezoneService;
    return service.getDateTime(this.get('offset'));
  }.property('offset', 'currentTime');
});