Модель просмотра IIFE кажется неопределенной

Я использую Mithril.JS, и похоже, что моя виртуальная машина не определена, а раньше ее не было.

Я искал вокруг, и там очень мало информации о mithril.js.

Код:

var app = {};

var apiData;

app.getData = function () {
  m.request({
    method: 'GET',
    url: '/api/stocks',
  }).then(function(data){
    data = apiData;
  })
};

app.App = function(data){ // model class
  this.plotCfg = {
    chart: {
        renderTo: "plot"
    },
    rangeSelector: {
        selected: 4
    },
    yAxis: {
        labels: {
            formatter: function () {
                return (this.value > 0 ? ' + ' : '') + this.value + '%';
            }
        },
        plotLines: [{
            value: 0,
            width: 2,
            color: 'silver'
        }]
    },

    plotOptions: {
        series: {
            compare: 'percent',
            showInNavigator: true
        }
    },

    tooltip: {
        pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
        valueDecimals: 2,
        split: true
    },

      series: [{
          name: 'Kyle\'s Chart',
          data: apiData
      }]
  };
};
app.controller = function() { // controller
  this.apk = new app.App();
  this.cfg = this.apk.plotCfg;
};
app.plotter = function(ctrl) { // config class
  return function(elem,isin) {
      if(!isin) {
        m.startComputation();
        var chart = Highcharts.StockChart(ctrl.cfg);
        m.endComputation();
      }
  };
};
app.view = function(ctrl) { // view
  return m("#plot[style=height:400px]", {config: app.plotter(ctrl)})
};

app.Stock = function(data) {
  this.date_added = m.prop(new Date());
  this.symbol = m.prop(data.symbol);
  this.id = m.prop(data.id)
};

app.SymbolList = Array;

app.vm = (function() {
    var vm = {}
    vm.init = function() {
        //a running list of todos
        vm.list = new app.SymbolList();
        //a slot to store the name of a new todo before it is created
        app.parseData = function (data) {
          for (var i =0; i< list.length ;i++) {
            console.log(list[i].stock);
            var stockSymbol = data[i].stock;
            vm.list.push(new app.Stock({symbol : stockSymbol}));
        }
        app.parseData(apiData);
        vm.symbol = m.prop("");
        //adds a todo to the list, and clears the description field for user convenience
        vm.add = function() {
            var data = vm.symbol();
            if (vm.symbol()) {
                data = {'text': data.toUpperCase()};
                m.request({method: 'POST',
                            url: '/api/stocks',
                            data: data,
                          }).then(function(list) {
                            vm.list = [];
                            for (var i =0; i< list.length ;i++) {
                              console.log(list[i].stock);
                              var stockSymbol = list[i].stock;
                              vm.list.push(new app.Stock({symbol : stockSymbol}));
                            }
                            return;
                          })
                vm.symbol("");
            }
        };
    }
    return vm
  }
}())

app.controller2 = function() {
  app.vm.init();
}
app.view2 = function() {
  return [
      m('input', { onchange: m.withAttr('value', app.vm.symbol),  value: app.vm.symbol()}),
      m('button.btn.btn-active.btn-primary', {onclick: app.vm.add}, 'Add Stock'),
      m('ul', [
        app.vm.list.map(function(item , index) {
          return m("li", [
            m('p', item.symbol())
          ])
        })
      ])
  ]
};
m.mount(document.getElementById('chart'), {controller: app.controller, view: app.view}); //mount chart
m.mount(document.getElementById('app'), {controller: app.controller2, view: app.view2}); //mount list
<div id="app"></div>
<div id="chart"></div>
<script src="https://cdn.rawgit.com/lhorie/mithril.js/v0.2.5/mithril.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>

Ошибка, которая появляется в хроме, заключается в следующем:

app.js:119 Uncaught TypeError: Cannot read property 'init' of undefined
    at new app.controllerT (app.js:119)
    at ea (mithril.js:1408)
    at Function.k.mount.k.module (mithril.js:1462)
    at app.js:135

Все было хорошо, пока я не добавил вторую точку монтирования, представление и контроллер.

Любые идеи?


person Quesofat    schedule 19.01.2017    source источник


Ответы (1)


Проблема в том, что app.vm не раскрывает init.

Текущий код для app.vm выглядит так:

app.vm = (function(){
  var vm = {}
  vm.init = function(){
    /* lots of stuff... */

    return vm
  }
}())

Это означает, что внутренний vm.init возвращает vm, но app.vm IIFE ничего не возвращает. Должен быть:

app.vm = (function(){
  var vm = {}
  vm.init = function(){
    /* lots of stuff... */
  }
  return vm
}())

Очень трудно читать структуру вашего приложения, потому что она полна множества экзотических шаблонов, которые кажутся бесполезными. По общему признанию, закрытие 'vm' - это шаблон, представленный в руководствах по Mithril, но я думаю, что гораздо проще писать, анализировать и отлаживать приложения, если мы избежим всех этих замыканий, вызовов инициализации, конструкторов, вложенных объектов и пространств имен.

Идея, лежащая в основе «моделей просмотра», исходит из состояния разработки веб-приложений, когда Mithril был первоначально выпущен (начало 2014 г.), когда одной из основных проблем при разработке интерфейсных приложений было кажущееся отсутствие структуры, и Mithril счел необходимым показать людям, как структурировать объекты. Но структура этой формы полезна только в том случае, если она проясняет намерение — в приведенном выше коде она все путает. Например, app.getData нигде не вызывается и присваивает пустую глобальную переменную своему собственному аргументу перед его удалением. Об этом было бы проще рассуждать, если бы у нас было меньше объектов.

Вот тот же код с некоторыми дополнительными исправлениями и альтернативной структурой. Принципы работы в этом рефакторинге:

  1. Мы больше не пишем какие-либо собственные конструкторы или замыкания, что приводит к менее динамичному выполнению кода и позволяет избежать возможных ошибок, таких как app.vm.init.
  2. Мы больше не привязываем вещи к объектам, если эта структура не является полезной или значимой, и не используем простые переменные или объявляем вещи в точке использования, если они используются только один раз, что приводит к меньшему количеству ссылок и меньшей структурной сложности.
  3. Мы используем литералы объектов — var x = { y : 'z' } вместо var x = {}; x.y = 'z', поэтому мы можем видеть целостные структуры, а не мысленно интерпретировать выполнение кода, чтобы понять, как объекты будут создаваться во время выполнения.
  4. Вместо того, чтобы использовать один большой общий app.vm для хранения всего, мы разделяем нашу модель приложения на места, где они актуальны, и используем функции для передачи значений из одного места в другое, что позволяет нам разделить нашу сложность. Я подробнее остановлюсь на этом после того, как покажу код:

// Model data
var seriesData = []

// Model functions
function addToSeries(data){
  seriesData.push.apply(seriesData,data)
}

function getData( symbol ){
  m.request( {method: 'POST',
    url: '/api/stocks',
    data: { text : symbol.toUpperCase() },
  } ).then(function(list) {
    return list.map(function( item ){
      return makeStock( { symbol : item.stock } )
    } )
  } )
}

function makeStock( data ) {
  return {
    date_added : new Date(),
    symbol     : data.symbol,
    id         : data.id
  }
}

// View data
var chartConfig = {
  rangeSelector: {
    selected: 4
  },
  yAxis: {
    labels: {
      formatter: function () {
        return (this.value > 0 ? ' + ' : '') + this.value + '%';
      }
    },
    plotLines: [{
      value: 0,
      width: 2,
      color: 'silver'
    }]
  },

  plotOptions: {
    series: {
      compare: 'percent',
      showInNavigator: true
    }
  },

  tooltip: {
    pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
    valueDecimals: 2,
    split: true
  },

  series: [{
    name: 'Kyle\'s Chart',
    data: seriesData
  }]
}

// Components
var chartComponent = {
  view : function(ctrl) {
    return m("#plot[style=height:400px]", {
      config: function(elem,isin) {
        if(!isin)
          Highcharts.StockChart(elem, chartConfig)
      }
    })
  }
}

var todosComponent = {
  controller : function(){
    return {
      symbol : m.prop('')
    }
  },

  view : function( ctrl ){
    return [
      m('input', {
        onchange: m.withAttr('value', ctrl.symbol),
        value: ctrl.symbol()
      }),

      m('button.btn.btn-active.btn-primary', {
        onclick: function(){
          if( ctrl.symbol() )
            getData( ctrl.symbol() )
              .then( function( data ){
                addToSeries( data )
              } )

          ctrl.symbol('')
        }
      }, 'Add Stock'),

      m('ul',
        todos.map(function(item) {
          return m("li",
            m('p', item.symbol)
          )
        })
      )
    ]
  }
}

// UI initialisation
m.mount(document.getElementById('chart'), chartComponent)
m.mount(document.getElementById('app'), todosComponent)

Больше нет ни app, ни vm, ни list. В конечном итоге они бесполезны, потому что они настолько расплывчаты и универсальны, что используются для хранения всего — и когда один объект содержит все, вы также можете иметь эти вещи в свободном доступе.

Основной список динамических данных теперь называется seriesData. Это просто массив. Чтобы взаимодействовать с ним, у нас есть 3 простые функции для изменения данных ряда, получения новых данных и создания новой точки данных из ввода. Здесь нет необходимости ни в конструкторах, ни в реквизитах — реквизиты — это утилита Mithril для возможности удобного чтения и записи данных из ввода — они в любом случае несовместимы с API Highcharts.

Это все данные модели, которые нам нужны. Далее у нас есть код, специфичный для нашего пользовательского интерфейса. Объект конфигурации Highcharts ссылается на seriesData, но, кроме того, это эзотерический объект, написанный для соответствия API Highcharts. Мы опускаем renderTo, потому что это динамически определяется нашим пользовательским интерфейсом Mithril.

Затем идут компоненты, которые мы пишем как литералы объектов, а не собираем их вместе позже — контроллер компонента имеет смысл только по отношению к его представлению. chartComponent на самом деле не нуждается в контроллере, так как он не имеет состояния и просто считывает ранее определенные данные модели и представления. Мы передаем ссылку на элемент непосредственно в Highcharts API в функции config. Поскольку эта функция используется только один раз в одном месте, мы объявляем ее встроенной вместо того, чтобы определять ее в одном месте и привязывать ее где-то еще. start/endComputation не нужны, так как процесс синхронный и нет необходимости останавливать рендеринг мифрила во время этого процесса.

Я не мог понять, как должна работать модель «todos», но я предположил, что второй компонент предназначен для обеспечения альтернативного представления точек данных и позволяет пользователю вводить данные для определения и получения дополнительных данных. Здесь мы храним реквизит «символ» в контроллере, поскольку это свойство с отслеживанием состояния, которое используется исключительно представлением. Это наше единственное свойство с состоянием, относящееся к этому компоненту, так что это все, что мы определяем в контроллере. Ранее мы упростили связанные с моделью функции — теперь в представлении мы взаимодействуем с ними, явно передавая данные символа вместо того, чтобы определять их в другом месте и извлекать их в другом месте. Здесь мы также сбрасываем значение, так как это аспект логики этого компонента, а не общая модель данных.

person Barney    schedule 19.01.2017