2017-01-19 10 views
0

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

Я искал вокруг, и там очень мало с точки зрения 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 

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

Любые идеи?

ответ

3

Проблема в том, что 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, потому что это определяется динамически нашим Мифриловым интерфейсом.

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

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