简体   繁体   中英

How do you synchronise stateful controls in an Ember view?

I have a basic Ember application. It has 2 controls: a select element and a text field. The controls are used to change what data is visible. Using the select element you can choose to see all numbers, even numbers only, or odd numbers only. Using the text field you can search for numbers that contain the digits you input.

I want the controls need to behave indepentently. By that I mean that if I type into the text field after choosing an option from the select element, the select element should be reset. Likewise if I choose an option from the select element, the text field should be cleared.

I find this difficult to implement because if I call this.set('query', '') in select 's observer, I will trigger the query 's observer which is programmed to set content to all the numbers when query === '' . In other words, this approach makes it so that choosing even numbers will cause the text field to clear which will cause all the numbers to be visible which is wrong since even numbers were chosen!

The way I got it to work way by putting a check for the value of select in the query_changed method. This feels like a bad solution. I imagine that if there were many more than 2 controls, this approach would fill up observers with many if statements.

query_changed: function() {
    var query = this.get('query');
    if (query === '') {
        if (this.get('select').value === '') {  // <-- is there a better way?
            this.set('content', this.get('numbers'));
        }
    } else {
        var output = $.map(this.get('numbers'), function(number, i) {
            if (_.string.include(number, query)) {
                return number;
            }
            return null;
        });
        this.set('content', output);
    }
}.observes('query')

I tried a few other ideas, but they were all basically like the above.

How are you supposed to handle this in Ember? I looked at Ember.StateManager and it looks like it is meant to handle multiple views. Here I have only one view with stateful controls.

Demo: http://jsfiddle.net/DxbeB/1/

The code without anything to synchronize the controls:

App = Ember.Application.create({});
App.controller = Ember.Object.create({
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
    contentBinding: Ember.Binding.oneWay('numbers'),
    select: null,
    query: null,
    select_changed: function() {
        if (this.get('select').value === 'all') {
            this.set('content', this.get('numbers'));
        }
        if (this.get('select').value === 'even') {
            var output = $.map(this.get('numbers'), function(number, i) {
                if (number % 2 === 0) {
                    return number;
                }
                return null;
            });
            this.set('content', output);
        }
        if (this.get('select').value === 'odd') {
            var output = $.map(this.get('numbers'), function(number, i) {
                if (number % 2 !== 0) {
                    return number;
                }
                return null;
            });
            this.set('content', output);
        }

    }.observes('select'),
    query_changed: function() {
        var query = this.get('query');
        if (query === '') {
            this.set('content', this.get('numbers'));
        } else {
            var output = $.map(this.get('numbers'), function(number, i) {
                if (_.string.include(number, query)) {
                    return number;
                }
                return null;
            });
            this.set('content', output);
        }
    }.observes('query')
});

App.Select = Ember.Select.extend({
    content: [{label: '', value: ''}, {label: 'All numbers', value: 'all'}, {label: 'Even numbers', value: 'even'}, {label: 'Odd numbers', value: 'odd'}],
    optionLabelPath: 'content.label',
    optionValuePath: 'content.value'
});

App.MyView = Ember.View.extend({
    controllerBinding: 'App.controller'
});

The template:

{{#view App.MyView}}
    {{view App.Select selectionBinding="controller.select"}}
    {{view Ember.TextField valueBinding="controller.query"}}
    <p>{{controller.content}}</p>
{{/view}}

You could observe when the odd/even filter value is set on the controller and then reset the query value and vice versa, see http://jsfiddle.net/pangratz666/bWT4E/ :

Handlebars :

<script type="text/x-handlebars" >
    {{#view App.MyView}}
        {{view App.Select selectionBinding="select"}}
        {{view Ember.TextField valueBinding="query"}}
        <hr/>
        {{#each controller.filtered}}
            {{this}}
        {{/each}}   
    {{/view}}
</script>​

JavaScript :

App = Ember.Application.create({
    oddFilter: function(value) {
        return value % 2 === 1;
    },
    evenFilter: function(value) {
        return value % 2 === 0;
    },
    queryFilter: function(query) {
        return function(value) {
            return (value + '').indexOf(query) !== -1;
        };
    }
});

App.Select = Ember.Select.extend({
    prompt: 'define filter',
    content: ['odd', 'even']
});

App.controller = Ember.ArrayProxy.create({
    content: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],

    _queryChanged: function() {
        var q = this.get('query');
        if (!Ember.empty(q)) {
            this.set('select', null);
        }
    }.observes('query'),

    _selectChanged: function() {
        var s = this.get('select');
        if (!Ember.none(s)) {
            this.set('query', null);
        }
    }.observes('select'),

    filtered: function() {
        var s = this.get('select');
        var q = this.get('query');
        var filter = Ember.K;
        if (s === 'odd') {
            filter = App.get('oddFilter');
        } else if (s === 'even') {
            filter = App.get('evenFilter');
        } else if (!Ember.empty(q)) {
            filter = App.get('queryFilter')(q);
        }

        return this.filter(filter, this);
    }.property('select', 'query').cacheable()
});

App.MyView = Ember.View.extend({
    controllerBinding: 'App.controller',
    selectBinding: 'controller.select',
    queryBinding: 'controller.query'
});​

Regarding your question if (this.get('select').value === '') { // <-- is there a better way? : Always access properties via set and get . This assures that the Binding stuff in Ember works as expected. Also, there are some utilities functions, so checking for an empty string can be done via Ember.empty(value) . See a blog post about that: http://code418.com/blog/2012/03/08/useful-emberjs-functions/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM