2016-12-02 7 views
1

Я только начинаю с handlebars, и я пытаюсь сделать простой двойной цикл, чтобы иметь весь день с 15 минутные интервалы. Очень странно, что если ребенок и родитель имеют одинаковые значения, вместо этого возвращается объект view. Это мой код:../this возвращает объект вида внутри внутреннего цикла, когда родитель и ребенок имеют одинаковое значение

var handlebarsExpress = require('express-handlebars').create({ 
    helpers: { 
     for: function(from, to, incr, block) { 
      var accum = ''; 
      for(var i = from; i <= to; i += incr) { 
       accum += block.fn(i); 
      } 
      return accum; 
     } 
    } 
}); 

app.engine('handlebars', handlebarsExpress.engine); 
app.set('view engine', 'handlebars'); 

... 
... 

В файле вида (sth.handlebars) у меня есть это:

<div class="row columns large-12"> 
    {{#for 0 23 1}} 
     {{#for 0 45 15}} 
      {{log ../this}} 
      <span>{{../this}}:{{this}}</span><br> 
     {{/for}} 
    {{/for}} 
</div> 

Вывод HTML-то вроде этого:

[object Object]:0 
0:15 
0:30 
0:45 
1:0 
... 
... 
13:45 
14:0 
14:15 
14:30 
14:45 
15:0 
[object Object]:15 
15:30 
15:45 
16:0 
16:15 

Помощник log сообщает в терминале:

{ settings: 
    { 'x-powered-by': true, 
    etag: 'weak', 
    'etag fn': [Function: wetag], 
    env: 'development', 
    'query parser': 'extended', 
    'query parser fn': [Function: parseExtendedQueryString], 
    'subdomain offset': 2, 
    'trust proxy': false, 
    'trust proxy fn': [Function: trustNone], 
    view: [Function: View], 
    views: '/home/elsa/Projects/rental-borg/project/views', 
    'jsonp callback name': 'callback', 
    'view engine': 'handlebars', 
    port: 8765 }, 
    flash: { info: undefined, error: undefined }, 
    _locals: { flash: { info: undefined, error: undefined } }, 
    cache: false } 
0 
0 
0 
1 
1 
1 
1 
2 
2 
2 
2 
3 
3 
3 
3 
4 
... 
... 
13 
13 
13 
13 
14 
14 
14 
14 
15 
{ settings: 
    { 'x-powered-by': true, 
    etag: 'weak', 
    'etag fn': [Function: wetag], 
    env: 'development', 
    'query parser': 'extended', 
    'query parser fn': [Function: parseExtendedQueryString], 
    'subdomain offset': 2, 
    'trust proxy': false, 
    'trust proxy fn': [Function: trustNone], 
    view: [Function: View], 
    views: '/home/elsa/Projects/rental-borg/project/views', 
    'jsonp callback name': 'callback', 
    'view engine': 'handlebars', 
    port: 8765 }, 
    flash: { info: undefined, error: undefined }, 
    _locals: { flash: { info: undefined, error: undefined } }, 
    cache: false } 
15 
15 
16 
16 
16 
16 
... 
23 
23 
23 
23 

Очевидно, что когда this и ../this равны, ../this фактически возвращает что-то вроде ../../this, который является объектом представления, я полагаю. Это происходит дважды в моем конкретном случае, в 00:00 и 15:15.

Это ошибка?

EDIT: Я решил проблему с новой функцией Block Parameters Handlebars 3, но исходный вопрос остается. Это то, что я сделал, чтобы решить эту проблему:

.handlebars файл:

{{#for 0 23 1 as |hour|}} 
    {{#for 0 45 15 as |minute|}} 
     <span>{{hour}}:{{minute}}</span> 
    {{/for}} 
{{/for}} 

и помощник:

for: function(from, to, incr, block) { 
    var args = [], options = arguments[arguments.length - 1]; 
    for (var i = 0; i < arguments.length - 1; i++) { 
     args.push(arguments[i]); 
    } 

    var accum = ''; 
    for(var i = from; i <= to; i += incr) { 
     accum += options.fn(i, {data: options.data, blockParams: [i]}); 
    } 
    return accum; 
}, 

ответ

4

Это было интригующее открытие, и я действительно хотел, чтобы найти причину таких казалось бы, странное поведение. Я проделал некоторую рывку в Handlebars source code, и я нашел соответствующий код, который был найден на line 176 of lib/handlebars/runtime.js.

Руль хранит стопку объектов контекста, верхним объектом которого является ссылка {{this}} в текущем исполняемом блоке шаблона Handlebars. Когда в шаблоне выполнения встречаются вложенные блоки, в стек помещается новый объект. Это то, что позволяет код вроде следующего:

{{#each this.list}} 
    {{this}} 
{{/each}} 

На первой линии, this объект с перечислимой собственности под названием «список». В строке 2 является итерированным элементом объекта list. Как указывает вопрос, Handlebars позволяет нам использовать ../ для доступа к объектам контекста более глубоко в стеке. В приведенном выше примере, если мы хотим получить доступ к объекту list из вспомогательного помощника #each, нам пришлось бы использовать {{../this.list}}.

С этим кратким изложением в сторону остается вопрос: почему наш стек контекста, кажется, ломается, когда значение текущей итерации внешнего цикла for равно количеству цикла внутреннего цикла for?

relevant code от источника Рули является следующее:

let currentDepths = depths; 
if (depths && context != depths[0]) { 
    currentDepths = [context].concat(depths); 
} 

depths является внутренний стек контекста объектов, context является объект, переданный в настоящее время исполняющего блока, и currentDepths является стек контекста, который сделан доступный исполняющему блоку. Как вы можете видеть, текущий context помещается в доступный стек , только еслиcontext не loosely equal до текущей вершины стека, depths[0].

Давайте применим эту логику к коду в вопросе.

Когда контекст внешнего #for блока 15 и контекст внутренней #for блока 0:

  • depths является: [15, {ROOT_OBJECT}] (где {ROOT_OBJECT} означает объект, который был аргумент template метод call.)
  • Becuase 0 != 15, currentDepths будет: [0, 15, {ROOT_OBJECT}].
  • Внутри внутреннего блока #for шаблона, {{this}} является 0, {{../this}} является 15 и {{../../this}} является {ROOT_OBJECT}.

Однако, когда внешние и внутренние блоки #for каждый имеет контекстное значение 15, мы получаем следующее:

  • depths является: [15, {ROOT_OBJECT}].
  • 15 == 15, currentDepths = depths = [15, {ROOT_OBJECT}].
  • Внутри внутреннего блока #for шаблона, {{this}} является 15, {{../this}} является {ROOT_OBJECT}, и это {{../../this}}undefined.

Именно поэтому кажется, что ваш {{../this}} пропускает уровень, когда внешний и внутренний блоки #for имеют одинаковое значение. Это на самом деле потому, что значение внутреннего #for равно , а не толкается в стек контекста!

В этот момент мы должны спросить, почему Handlebars ведет себя таким образом и определяет, является ли это признаком или ошибкой.

Так получилось, что этот код был добавлен намеренно для решения issue, который пользователи испытывали с Handlebars.Проблема может быть продемонстрирована с помощью примера:

Предполагая, что контекстное объект:

{ 
    config: { 
     showName: true 
    }, 
    name: 'John Doe' 
} 

пользователей нашли следующую случай использования, чтобы быть нелогичным:

{{#with config}} 
    {{#if showName}} 
     {{../../name}} 
    {{/if}} 
{{/with}} 

конкретный вопрос был с необходимостью для двойного ../ для доступа к корневому объекту: {{../../name}}, а не {{../name}}. Пользователи почувствовали, что, поскольку объект контекста в пределах {{#if showName}} был объектом config, то повышение уровня одного уровня, ../, должно получить доступ к «родительскому» объекту config - корневому объекту. Причина, по которой два шага были необходимы, заключалась в том, что Handlebars создавал объект стека контекста для каждого вспомогательного блока блока. Это означает, что для доступа к корневому контексту требуется два шага; первый шаг получает контекст {{#with config}}, а второй шаг получает контекст корня.

A commit было сделано, что предотвращает толкания контекста объекта доступного стека контекста, когда новый объект контекста является свободно равен к объекту в верхней части стека контекста. Ответственный код - это исходный код, который мы рассмотрели выше. Начиная с version 4.0.0 из Handlebars.js наш пример конфигурации завершится с ошибкой. Теперь требуется только один шаг ../.

Возвращаясь к примеру кода в исходный вопрос, причиной того, что 15 от внешнего блока #for определяется как равный 15 во внутреннем блоке #for происходит из-за того, как числовые типы сравниваются в JavaScript; два объекта типа Number равны, если они имеют одинаковое значение . Это контрастирует с типом объекта, для которого два объекта равны, только если они ссылаются на на тот же объект в памяти. Это означает, что если мы повторно написали исходный пример кода для использования типов объектов вместо типов номеров для контекстов, то мы никогда не встретим условного оператора сравнения, и мы бы всегда имели ожидаемый стек контекста в нашем внутреннем блоке #for.

для цикла в нашем помощнике будет обновляться, чтобы передать тип объекта в контексте кадра он создает:

for(var i = from; i <= to; i += incr) { 
    accum += block.fn({ value: i }); 
} 

И наш шаблону теперь нужно будет получить доступ к соответствующему свойству этого объекта:

{{log ../this.value}} 
<span>{{../this.value}}:{{this.value}}</span><br> 

С этими исправлениями вы должны найти свой код, как и ожидалось.

В некоторой степени субъективно объявлять, является ли это ошибкой с ручками. Условие условно было добавлено намеренно, и полученное поведение делает то, что он должен был сделать. Тем не менее, мне трудно представить случай, когда это поведение будет ожидаться или желательно, когда вовлеченные контексты являются примитивами и не Типы объектов. Может быть разумным, чтобы код Handlebars был сделан для сравнения по типам объектов только. Я думаю, что здесь есть законный случай: open an issue.

+0

Удивительный ответ, спасибо большое, мне было любопытно, но у меня не было времени для расследования – hakermania

 Смежные вопросы

  • Нет связанных вопросов^_^