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

时间:2018-09-11 20:38:55

标签: javascript meteor nouislider

我可以使用JavaScript毫无问题地更新div。例如,两个div的初始值都显示在左侧的图像中,而单击测试按钮后的值则显示在右侧。

enter image description here

当我单击“测试”按钮时,计数器将增加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 + " ");

到目前为止很好,但是现在我想更改div的顺序:当计数器为偶数时,我希望div a =黄色在顶部(div b =灰色在底部),而另一个计数器不平衡时会消失。

为简单起见,在示例中,我对每个可能的命令(a,b)和(b,a)使用2个小数组,并且一个助手“命令”将根据计数器值(x)返回2种情况中的1种%2是否为零)

不幸的是,结果不是我想要和期望的。 div正确更改了位置,但其文本却没有。第一次单击后,计数器从5变为6,因此黄色div从下到上移动,但是div内的文本是错误的,我希望黄色div中的'new value a'向上移动,但是我得到了取而代之的是“新值ba”(反之,与其他div相同)

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

strange console output, contradicting id's

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

我知道我可以使用助手在正确的div中获得正确的文本。但是我的最终目标不是更改div内的文本,而是要使用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 + " ");
}

1 个答案:

答案 0 :(得分:3)

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

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

然后,您可以轻松地使用Template的内置jQuery获取nouislider将呈现的目标div。

请考虑以下模板:

<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
  • (+)将滑块状态与值状态分开
  • (-)渲染可以进行多次以进行重新渲染,用户不会注意到,但是浪费资源,这在移动设备上可能是个问题。
  • (-)在较大的模板上可能变得过于复杂。模板的封装在这里非常重要。