Подтвердить что ты не робот

Как динамически добавлять и удалять представления с помощью Ember.js

Я пытаюсь создать интерфейс для перемещения таблиц в базе данных отношений. Каждый выбор представляет собой столбец. Если столбец является внешним ключом, новый справа добавляется. Это происходит для каждого внешнего ключа, к которому пользователь обращается. Число выборок является динамическим.

Я сделал ошибку с ошибкой, у которой есть код, который вручную добавляет и удаляет выбранные виды. Я думаю, что он, вероятно, может быть заменен лучшим Ember-кодом (может быть, какой-то объект массива?), Я просто не уверен, как наилучшим образом использовать фреймворк для этой проблемы.

Здесь мой JSBin http://jsbin.com/olefUMAr/3/edit

HTML:

<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Ember template" />
<meta charset=utf-8 />
<title>JS Bin</title>
  <script src="http://code.jquery.com/jquery-1.9.0.js"></script>
  <script src="http://builds.emberjs.com/handlebars-1.0.0.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.1.2/ember.js"></script>
</head>
<body>
  <script type="text/x-handlebars" data-template-name="my_template">
    {{view fieldSelects}}
  </script>

  <div id="main"></div>
</body>
</html>

JavaScript:

App = Ember.Application.create();

var TemplatedViewController = Ember.Object.extend({
    templateFunction: null,
    viewArgs: null,
    viewBaseClass: Ember.View,
    view: function () {
        var controller = this;
        var viewArgs = this.get('viewArgs') || {};
        var args = {
            template: controller.get('templateFunction'),
            controller: controller
        };
        args = $.extend(viewArgs, args);
        return this.get('viewBaseClass').extend(args);
    }.property('templateFunction', 'viewArgs'),
    appendView: function (selector) {
        this.get('view').create().appendTo(selector);
    },
    appendViewToBody: function () {
        this.get('view').create().append();
    }
});

var DATA = {};
DATA.model_data = {
  "Book": {
    "fields": [
      "id",
      "title",
      "publication_year",
      "authors"
    ],
    "meta": {
      "id": {},
      "title": {},
      "publication_year": {},
      "authors": {
        "model": "Author"
      }
    }
  },
  "Author": {
    "fields": [
      "id",
      "first_name",
      "last_name",
      "books"
    ],
    "meta": {
      "id": {},
      "first_name": {},
      "last_name": {},
      "books": {
        "model": "Book"
      }
    }
  }
};

var Controller = TemplatedViewController.extend({
    view: function () {
        var controller = this;
        return this.get('viewBaseClass').extend({
            controller: controller,
            templateName: 'my_template'
        });
    }.property(),
    selectedFields: null,
    fieldSelects: function () {
        var filter = this;
        return Ember.ContainerView.extend({
            controller: this,
            childViews: function () {
                var that = this;
                var selectedFields = filter.get('selectedFields');

                var ret = [];
                var model = 'Book';
                selectedFields.forEach(function (item, index, enumerable) {
                    var selection = item;
                    if (model) {
                        var select = that.makeSelect(model, that.getPositionIndex(), selection, true).create();
                        ret.pushObject(select);
                        model = DATA.model_data[model].meta[selection].model;
                    }
                });

                return ret;
            }.property(),
            nextPositionIndex: 0,
            incrementPositionIndex: function () {
                this.set('nextPositionIndex', this.get('nextPositionIndex') + 1);
            },
            getPositionIndex: function () {
                var index = this.get('nextPositionIndex');
                this.incrementPositionIndex();
                return index;
            },
            setNextPositionIndex: function (newValue) {
                this.set('nextPositionIndex', newValue+1);
            },
            makeSelect: function (modelName, positionIndex, selection, isInitializing) {
                var view = this;
                return Ember.Select.extend({
                    positionIndex: positionIndex,
                    controller: filter,
                    content: DATA.model_data[modelName].fields,
                    prompt: '---------',
                    selection: selection || null,
                    selectionChanged: function () {
                        var field = this.get('selection');

                        // Remove child views after this one
                        var lastIndex = view.get('length') - 1;
                        if (lastIndex > this.get('positionIndex')) {
                            view.removeAt(this.get('positionIndex')+1, lastIndex-this.get('positionIndex'));
                            view.setNextPositionIndex(this.get('positionIndex'));
                        }

                        if (! isInitializing && DATA.model_data[modelName].meta[field].model) {
                            var relatedModel = DATA.model_data[modelName].meta[field].model;
                            view.pushObject(view.makeSelect(relatedModel, view.getPositionIndex()).create());
                        }

                        // Reset ``isInitializing`` after the first run
                        if (isInitializing) {
                            isInitializing = false;
                        }

                        var selectedFields = [];
                        view.get('childViews').forEach(function (item, index, enumerable) {
                            var childView = item;
                            var selection = childView.get('selection');
                            selectedFields.pushObject(selection);
                        });
                        filter.set('selectedFields', selectedFields);
                    }.observes('selection')
                });
            }
        });
    }.property()
});
var controller = Controller.create({
    selectedFields: ['authors', 'first_name']
});

$(function () {
    controller.appendView('#main');
});
4b9b3361

Ответ 1

Подход:

Я бы решил эту проблему, используя Ember Component. Я использовал компонент, потому что это будет:

  • Легко повторное использование

  • Код является самодостаточным и не имеет внешних требований к любому другому вашему коду.

  • Мы можем использовать простой javascript для создания представления. Обычный javascript должен облегчить понимание потока кода (потому что вам не нужно знать, что делает Ember с расширенными объектами за кулисами), и у него будет меньше накладных расходов.

Демо:

Я создал здесь JSBin здесь, код ниже.

Использование

Добавьте к шаблону дескрипторов:

{{select-filter-box data=model selected=selected}}

Создайте тэг select-filter-box, а затем привяжите свою модель к атрибуту data и вашему массиву значений selected к атрибуту selected.

Приложение:

App = Ember.Application.create();

App.ApplicationController = Ember.ObjectController.extend({
  model: DATA.model_data,
  selected: ['Author','']
});

App.SelectFilterBoxComponent = Ember.Component.extend({

  template: Ember.Handlebars.compile(''), // Blank template
  data: null,
  lastCount: 0,
  selected: [],
  selectedChanged: function(){

    // Properties required to build view
    var p = this.getProperties("elementId", "data", "lastCount", "selected");

    // Used to gain context of controller in on selected changed event
    var controller = this;

    // Check there is at least one property. I.e. the base model.
    var length = p.selected.length;
    if(length > 1){

      var currentModelName = p.selected[0];
      var type = {};

      // This function will return an existing select box or create new
      var getOrCreate = function(idx){

        // Determine the id of the select box
        var id = p.elementId + "_" + idx;

        // Try get the select box if it exists
        var select = $("#" + id); 
        if(select.length === 0){

          // Create select box
          select = $("<select id='" + id +"'></select>");

          // Action to take if select is changed. State is made available through evt.data
          select.on("change", { controller: controller, index: idx }, function(evt){

            // Restore the state
            var controller = evt.data.controller;
            var index = evt.data.index;
            var selected = controller.get("selected");

            // The selected field
            var fieldName = $(this).val();

            // Update the selected
            selected = selected.slice(0, index);
            selected.push(fieldName);
            controller.set("selected", selected);
          });

          // Add it to the component container
          $("#" + p.elementId).append(select);
        }
        return select;
      };

      // Add the options to the select box
      var populate = function(select){
        // Only populate the select box if it doesn't have the correct model
        if(select.data("type")==currentModelName)
          return;

        // Clear any existing options
        select.html("");

        // Get the field from the model
        var fields = p.data[currentModelName].fields;

        // Add default empty option
         select.append($("<option value=''>------</option>"));

        // Add the fields to the select box
        for(var f = 0; f < fields.length; f++)
          select.append($("<option>" + fields[f] + "</option>"));

        // Set the model type on the select
        select.data("type", currentModelName);
      };

      var setModelNameFromFieldName = function(fieldName){

        // Get the field type from current model meta
        type = p.data[currentModelName].meta[fieldName];

        // Set the current model
        currentModelName = (type !== undefined && type.model !== undefined) ? type.model : null;
      };

      // Remove any unneeded select boxes. I.e. where the number of selects exceed the selected length
       if(p.lastCount > length)
        for(var i=length; i < p.lastCount; i++)
          $("#" + p.elementId + "_" + i).remove();

      this.set("lastCount", length);

      // Loop through all of the selected, to build view
      for(var s = 1; s < length; s++)
      { 
        // Get or Create select box at index s
        var select = getOrCreate(s);

        // Populate the model fields to the selectbox, if required
        populate(select);

        // Current selected
        var field = p.selected[s];

        // Ensure correct value is selected
        select.val(field);

        // Set the model for next iteration
        setModelNameFromFieldName(field);

        if(s === length - 1 && type !== undefined && type.model !== undefined)
        {
          p.selected.push('');
          this.notifyPropertyChange("selected");
        }
      }      
    }    
  }.observes("selected"),

  didInsertElement: function(){
    this.selectedChanged();
  }
});

Как это работает

Компонент принимает два параметра model и selected, затем привязывает наблюдателя к свойству selected. Каждый раз, когда выбор изменяется либо через взаимодействие пользователя с полями выбора, либо с помощью свойства, связанного с selected, представление будет переопределено.

В коде используется следующий подход:

  • Определите, является ли массив выбора (selected) более 1. (поскольку первым значением должна быть базовая модель).

  • Прокрутите все выбранные поля i, начиная с индекса 1.

    • Определите, существует ли флажок i. Если не создать поле выбора.
    • Определите, имеет ли поле выбора i правильные поля моделей, основанные на текущей заполненной модели. Если да, ничего не делайте, если не заполняете поля.
    • Задайте текущее значение поля выбора.
    • Если мы последний поле выбора и поле выбрали ссылки на модель, затем нажмите пустое значение на выбор, чтобы вызвать следующий снимок.

  • При создании окна выбора обработчик onchange подключается, чтобы обновить значение selected, разрезав массив selected справа от текущего индекса и добавив его собственное значение. Это приведет к необходимости изменения представления.

  • Свойство count отслеживает предыдущую длину selected, поэтому, если сделано изменение к выбору, которое уменьшает текущую длину значений selected, то ненужные поля выбора могут быть удалены.

Исходный код прокомментирован, и я надеюсь, что это понятно, если у вас есть какие-либо вопросы с запросами, как это работает, не стесняйтесь спрашивать, и я попытаюсь объяснить это лучше.

Ваша модель:

Посмотрев на свою модель, рассмотрели ли вы ее упрощение ниже? Я ценю, что вы, возможно, не сможете, по другим причинам, выходящим за рамки вопроса. Просто мысль.

DATA.model_data = {
  "Book": {
    "id": {},
    "title": {},
    "publication_year": {},
    "authors": { "model": "Author" }
  },
  "Author": {
    "id": {},
    "first_name": {},
    "last_name": {},
    "books": { "model": "Book" }
  }
};

Таким образом, имена полей будут считаны с ключами объекта, а значение будет метаданных.


Надеюсь, вы сочтете это полезным. Сообщите мне, есть ли у вас какие-либо вопросы или проблемы.


Контроллер:

С этим компонентом вы можете использовать любой контроллер. В моей демонстрации компонента я использовал Ember, встроенный в ApplicationController для простоты.

Объяснение notifyPropertyChange():

Это вызвано тем, что когда мы вставляем новую строку в массив selected, используя функциональность push массивов.

Я использовал метод push, потому что это самый эффективный способ добавить новую запись в существующий массив.

В то время как у Ember есть метод pushObject, который также должен заботиться об уведомлении, я не смог его получить почитай это. Поэтому this.notifyPropertyChange("selected"); сообщает Ember, что мы обновили массив. Однако я надеюсь, что это не разбойник.


Альтернатива Ember Component - реализована как вид

Если вы не хотите использовать его в формате Component, вы можете реализовать его как представление. Это в конечном счете достигает одной и той же цели, но это может быть более знакомым шаблоном проектирования для вас.

См. этот JSBin для реализации в виде представления. Я не буду включать полный код здесь, потому что некоторые из них такие же, как и выше, вы можете увидеть его в JSBin

Использование:

Создайте экземпляр App.SelectFilterBoxView с контроллером, у которого есть свойство data и selected:

var myView = App.SelectFilterBoxView.create({
    controller: Ember.Object.create({
        data: DATA.model_data,
        selected: ['Author','']
    })
});

Затем добавьте представление по мере необходимости, например #main.

myView.appendTo("#main");

Ответ 2

К сожалению, ваш код не запускается, даже после добавления Ember в качестве библиотеки в JSFiddle, но ContainerView, вероятно, вы ищете: http://emberjs.com/api/classes/Ember.ContainerView.html, поскольку эти представления могут быть динамически добавлены/удалены.

Ответ 3

this.$().remove() или this.$().append(), вероятно, вы ищете:

Ember docs.