如何在没有助手的情况下更新 Meteor 中的 div?

How to update div in Meteor without helper?

我可以使用 JavaScript 更新 div 没有问题。例如,左侧图像显示了两个 div 的初始值,右侧显示了单击测试按钮后的值。

当我点击测试按钮时,计数器加 1,div 的值改变如下:

var ela = document.getElementById("a");
var elb = document.getElementById("b");
$("#" + ela.id).html("new value " + ela.id + " ");
$("#" + elb.id).html("new value " + elb.id + " ");

到目前为止一切顺利,但现在我想更改 divs 的顺序:当计数器为偶数时,我希望 div a = yellow 位于顶部(div b = 底部灰色), 计数器不均匀时反之.

为简单起见,我在示例中为每个可能的顺序 (a,b) 和 (b,a) 使用了 2 个小数组,并且助手 'order' 将 return 1 of the 2情况取决于计数器值(x%2 是否为零)

不幸的是,结果不是我想要和期望的。 div 正确地改变了位置,但它们的文本不是。第一次点击后计数器从 5 变为 6,所以黄色的 div 从底部移动到顶部,但是 divs 中的文本是错误的,我希望 'new value a' 在黄色 div 向上移动,但我得到 'new value b a' (其他 div 也一样)

此外,当我检查控制台输出中的 divs 时,我看到了奇怪的结果,Meteor 似乎对 div id 感到困惑?看下图,第一个div既是a又是b,同时是黄色和灰色...

有人知道这是为什么吗?我该如何解决?

我知道我可以使用帮助程序在正确的 div 中获取正确的文本。但我的最终目标不是更改 divs 中的文本,而是我想像这样使用 nouislider 创建一个滑块:

var noUiSlider = require('nouislider');
var sliderHtmlElement = document.getElementById("a");
var options = {
    start: [0, 100],
    range: {
        'min': [0],
        'max': [100]
    }
};
noUiSlider.create(sliderHtmlElement, options);

我的完整测试代码:

<template name="MyTemplate">
 {{x}}
 <br>
 <br>
 {{#each order}}
  <div class="{{label}}" id="{{label}}" 
        style="background-color: {{color}};">
            start value {{label}}
        </div>
  <br>
 {{/each}}
 <button class="test">test</button>
</template>

var order1;
var order2;

Template.MyTemplate.onCreated(function() {
 Session.set("x", 5);
 
 var or0 = [];
 or0["label"] = "a";
 or0["color"] = "yellow";

 var or1 = [];
 or1["label"] = "b";
 or1["color"] = "grey";

 order1 = [];
 order1[0] = or0;
 order1[1] = or1;
 
 order2 = [];
 order2[0] = or1;
 order2[1] = or0;
});

Template.MyTemplate.events({
 'click .test': function(event) {
  var varx = Session.get("x") + 1;
  Session.set("x", varx);
  createSliders();
 }
});

Template.MyTemplate.helpers({
 x: function() {
  return Session.get("x");
 },
 order: function() {
  if (Session.get("x") % 2 === 0) {
   return order1;
  } else {
   return order2;
  }
 }
});

function createSliders() {
 var ela = document.getElementById("a");
 var elb = document.getElementById("b");
 console.log(ela);
 console.log(elb);
 $("#" + ela.id).html("new value " + ela.id + " ");
 $("#" + elb.id).html("new value " + elb.id + " ");
}

对于 Blaze,您还必须显式导入 .css 文件才能应用样式:

// no slider without css
import 'nouislider/distribute/nouislider.css'
import noUiSlider from 'nouislider'

然后您可以轻松地使用模板的内置 jQuery 来获取目标 div nouislider 将在其中呈现。

考虑以下模板:

<template name="MyTemplate">
    <div>
        <div id="range"></div>
    </div>
    {{#if values}}
        <div>
            <span>values: </span>
            <span>{{values}}</span>
        </div>
    {{/if}}
    <button class="test">Show Slider</button>
</template>

现在让我们通过单击按钮将一个新的 nouislider 渲染到 ID 为 range 的 div 中:

Template.MyTemplate.events({
  'click .test': function (event, templateInstance) {
    createSliders(templateInstance)
  },
})

function createSliders (templateInstance) {
  // get the target using the template's jQuery
  const range = templateInstance.$('#range').get(0)
  noUiSlider.create(range, {
    start: [0, 100],
    range: {
      'min': [0],
      'max': [100]
    },
    connect: true
  })
}

现在您也可以在此处轻松添加一些反应性数据(但要避免 Session):

Template.MyTemplate.onCreated(function () {
  const instance = this
  instance.state = new ReactiveDict()
  instance.state.set('values', null)
})

... 并将其与一些数据连接起来。 noUiSlider 允许您连接到它的更新事件,从那里您可以将值传递到状态:

function createSliders (templateInstance) {
  // get slider, render slider...
  // ...
  range.noUiSlider.on('update', function (values, handle) {
    // update values, for instance use a reactive dict
    templateInstance.state.set('values', values)
  })
}

使用助手将值渲染到模板中:

Template.MyTemplate.helpers({
  values () {
    return Template.instance().state.get('values')
  }
})

导入您自己的 .css 文件以静态设置滑块样式:

#range {
  width: 300px;
  margin: 14px;
}

或使用 jQuery 的 css 动态设置样式。


更新:在更新的显示列表上正确呈现

您描述的问题是正确的,可以重现。但是也可以避免,使用Template.onRendered来控制可能发生渲染的点。

我将模板扩展为以下代码:

<template name="MyTemplate">
    {{#each sliders}}
        <div class="range">
            <div>
                <span>id:</span>
                <span>{{this.id}}</span>
            </div>
            <div id="{{this.id}}">
                {{#if ready}}{{slider this}}{{/if}}
            </div>
            {{#with values this.id}}
                <div>
                <span>values: </span>
                <span>{{this}}</span>
                </div>
            {{/with}}
        </div>
    {{/each}}

    <button class="test">Switch Sliders</button>
</template>

现在查看目标 div,它之前只分配了一个 id。现在有一个 {{#if ready}} 标志和一个函数,调用 {{slider this}}.

现在为了只在 DOM 最初被渲染时渲染,我们需要 Template.onRendered:

Template.MyTemplate.onRendered(function () {
  const instance = this
  instance.state.set('ready', true)
})

并将其添加到(更新的)助手中:

Template.MyTemplate.helpers({
  sliders () {
    return Template.instance().state.get('sliders')
  },
  values (sliderId) {
    return Template.instance().state.get('values')[sliderId]
  },
  slider (source) {
    createSliders(source.id, source.options, Template.instance())
  },
  ready() {
    return Template.instance().state.get('ready')
  }
})

现在我们还有一些问题需要解决。我们只想在开关发生变化时渲染,而不是在值更新时渲染。但是我们需要最新的值,以便将它们重新分配为下一次渲染中的起始位置(否则滑块将设置为起始值 0,100)。

为此,我们稍微更改 onCreated 代码:

Template.MyTemplate.onCreated(function () {

  // initial slider states
  const sliders = [{
    id: 'slider-a',
    options: {
      start: [0, 100],
      range: {
        'min': [0],
        'max': [100]
      },
      connect: true
    }
  }, {
    id: 'slider-b',
    options: {
      start: [0, 100],
      range: {
        'min': [0],
        'max': [100]
      },
      connect: true
    }
  },
  ]

  const instance = this
  instance.state = new ReactiveDict()
  instance.state.set('values', {}) // mapping values by sliderId
  instance.state.set('sliders', sliders)
})

现在,如果我们按下切换按钮,想要 a) 删除所有当前滑块及其事件等,以及 b) 将 sliders 数据更新为我们的新(反转)状态:

Template.MyTemplate.events({
  'click .test': function (event, templateInstance) {

    let sliders = templateInstance.state.get('sliders')
    const values = templateInstance.state.get('values')

    // remove current rendered sliders
    // and their events / prevent memory leak
    sliders.forEach(slider => {
      const target = templateInstance.$(`#${slider.id}`).get(0)
      if (target && target.noUiSlider) {
        target.noUiSlider.off()
        target.noUiSlider.destroy()
      }
    })

    // assign current values as
    // start values for the next newly rendered
    // sliders
    sliders = sliders.map(slider => {
      const currentValues = values[slider.id]
      if (currentValues) {
        slider.options.start = currentValues.map(n => Number(n))
      }
      return slider
    }).reverse()

    templateInstance.state.set('sliders', sliders)
  }
})

因为我们有多个滑块值要分别更新,所以我们还需要更改createSlider函数中的一些代码:

function createSliders (sliderId, options, templateInstance) {
  const $target = $(`#${sliderId}`)
  const target = $target.get(0)

  //skip if slider is already in target
  if ($target.hasClass('noUi-target')) {
    return
  }

  noUiSlider.create(target, options)

  target.noUiSlider.on('update', function (values, handle) {
    // update values by sliderId
    const valuesObj = templateInstance.state.get('values')
    valuesObj[sliderId] = values
    templateInstance.state.set('values', valuesObj)
  })
}

通过使用这种方法,您既有优点也有缺点。

  • (+) 模式可用于许多类似的问题
  • (+) 不需要 autorun
  • (+) 将滑块状态与值状态分开
  • (-) 重新渲染可以多次渲染,用户不会注意到,但它会浪费资源,这在移动设备上可能是个问题。
  • (-) 在较大的模板上可能会变得过于复杂。 Templates的封装在这里很重要