为什么这个 alpine.js x-for 循环 2 次
Why this alpine.js x-for looping 2 times
我使用 alpine.js 渲染一个带有 x-for 循环的模式,该循环填充 select。奇怪的行为,x-for 似乎循环了 2 次,我不明白为什么:
代码:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('load', () => ({
indicateurs: [],
indicateurDictionnaireForm: {},
typeIndicateurs: {},
detectedTypeIndicateur: '',
loadIndicateur() {
$('#overlay').fadeIn();
fetch('<%= request.getContextPath() %>/mesures.load.action')
.then(response => response.json())
.then(data => {
this.indicateurs = data;
$('#overlay').fadeOut();
})
.catch(error => {
});
},
deleteIndicateur(id) {
$.ajax({
type: "DELETE",
url: "<%= request.getContextPath() %>/mesures.delete.action?indicateurDictionnaireId=" + id,
}).then(() => this.loadIndicateur());
},
postForm() {
return {
submitData() {
$.ajax({
type: "POST",
url: "<%= request.getContextPath() %>/mesures.save.action",
data: JSON.stringify(this.indicateurDictionnaireForm),
dataType: "JSON",
contentType: "application/json; charset=utf-8",
}).then(() => {
this.loadIndicateur();
this.resetForm();
$('#modalIndicateur').modal('hide');
})
},
}
},
editIndicateur(id) {
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.load.type-indicateur.action"
}).then(data => {
this.typeIndicateurs = data;
}).then(
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.edit.action?indicateurDictionnaireId=" + id,
}).then(data => {
this.indicateurDictionnaireForm = data;
this.detectedTypeIndicateur = data.typeIndicateur.code;
this.loadIndicateur();
$('#modalIndicateur').modal('show');
})
);
},
resetForm() {
this.indicateurDictionnaireForm = {};
}
}))
})
</script>
模态代码:
<div class="modal" tabindex="-1" role="dialog" id="modalIndicateur">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><s:text name="indicateur.ce.add"/></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form action="<%= request.getContextPath() %>/save.action"
method="POST"
class="w-64 mx-auto" x-data="postForm()" @submit.prevent="submitData">
<div class="form-group" style="padding-left: 15px; padding-bottom: 5px">
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="libelle_fr">
<s:text name="indicateur.ce.libelle_fr"/></label>
<div class="col-md-8">
<input id="libelle_fr" type="text"
name="indicateurDictionnaireForm.libelleFr"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.libelleFr">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="libelle_nl">
<s:text
name="indicateur.ce.libelle_nl"/></label>
<div class="col-md-8">
<input id="libelle_nl" type="text"
name="indicateurDictionnaireForm.libelleNl"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.libelleNl">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="code">
<s:text name="indicateur.ce.code"/></label>
<div class="col-md-8">
<input id="code" type="text"
name="indicateurDictionnaireForm.typeIndicateurCode"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.code">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="code">
<s:text name="indicateur.ce.code"/></label>
<div class="col-md-4">
<div class="flex flex-col w-full md:w-2/3">
<select x-model="indicateurDictionnaireForm.typeIndicateurCode"
class="form-control input-sm">
<template x-for="option in typeIndicateurs" x-effect="console.log(detectedTypeIndicateur)">
<option :key="option.code"
:value="option.code"
selected="option.code === detectedTypeIndicateur"
x-effect="console.log('code:', option.code, ' type: ', detectedTypeIndicateur)"
x-text="option.libellefr"></option>
</template>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"
@click="resetForm()">
<s:text name="common.button.quitter"/>
</button>
<button type="submit" class="btn btn-primary">
<s:text name="common.button.save"/>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
使用 x-effect 标签,我尝试记录要在 select 标签中 selected 的检测项目的值和值本身。
这是模式打开时的控制台输出:
// first loop
<empty string>
code: RESULTAT type: <empty string>
code: INCIDENCE type: <empty string>
code: REALISATION type: <empty string>
// second loop
REALISATION
code: RESULTAT type: REALISATION
code: INCIDENCE type: REALISATION
code: REALISATION type: REALISATION
在第一个循环中,我看到函数 editIndicateur() 的值是空的,所以我假设,alpine.s 尝试在回调之前呈现模态,然后进行第二个循环。
您是否知道为什么模态框在值被函数 return 之前填充,以及为什么它会第二次循环?
感谢您的帮助
您将 select
元素绑定到 indicateurDictionnaireForm.typeIndicateurCode
变量,并将其选项绑定到 typeIndicateurs
变量(通过对其进行循环)。
所以当你调用editIndicateur
方法时:
- 首先,您通过 AJAX 调用仅加载 select 选项,然后通过
this.typeIndicateurs = data
将其传递给 Alpine.js。
- Alpine 检测到您更改了
typeIndicateurs
并呈现模板。这是第一个循环。但是此时 indicateurDictionnaireForm
或 detectedTypeIndicateur
均未设置,因此它们将为空。
- 之后您进行新的 AJAX 调用,在其中加载额外的表单数据并设置
indicateurDictionnaireForm
和 detectedTypeIndicateur
变量。
- Alpine.js 检测到您更改了两个附加变量并再次呈现模板。这是第二个循环。
如果您有两个单独的 AJAX 调用,这是正常行为,因为更改不会在同一个更新队列中。要解决此问题,您应该在 editIndicateur
函数内创建一个本地(非反应性)变量,然后在第一次 AJAX 调用时将选项的数组存储在该变量中。然后在第二个 AJAX 调用中将所有 3 个 Alpine.js 反应变量设置在一起,因此更改将在同一个更新队列中,因此 Alpine.js 只渲染模板一次。
editIndicateur(id) {
let indicators;
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.load.type-indicateur.action"
}).then(data => {
indicators = data;
}).then(
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.edit.action?indicateurDictionnaireId=" + id,
}).then(data => {
this.indicateurDictionnaireForm = data;
this.detectedTypeIndicateur = data.typeIndicateur.code;
this.typeIndicateurs = indicators;
this.loadIndicateur();
$('#modalIndicateur').modal('show');
})
);
},
我使用 alpine.js 渲染一个带有 x-for 循环的模式,该循环填充 select。奇怪的行为,x-for 似乎循环了 2 次,我不明白为什么:
代码:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('load', () => ({
indicateurs: [],
indicateurDictionnaireForm: {},
typeIndicateurs: {},
detectedTypeIndicateur: '',
loadIndicateur() {
$('#overlay').fadeIn();
fetch('<%= request.getContextPath() %>/mesures.load.action')
.then(response => response.json())
.then(data => {
this.indicateurs = data;
$('#overlay').fadeOut();
})
.catch(error => {
});
},
deleteIndicateur(id) {
$.ajax({
type: "DELETE",
url: "<%= request.getContextPath() %>/mesures.delete.action?indicateurDictionnaireId=" + id,
}).then(() => this.loadIndicateur());
},
postForm() {
return {
submitData() {
$.ajax({
type: "POST",
url: "<%= request.getContextPath() %>/mesures.save.action",
data: JSON.stringify(this.indicateurDictionnaireForm),
dataType: "JSON",
contentType: "application/json; charset=utf-8",
}).then(() => {
this.loadIndicateur();
this.resetForm();
$('#modalIndicateur').modal('hide');
})
},
}
},
editIndicateur(id) {
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.load.type-indicateur.action"
}).then(data => {
this.typeIndicateurs = data;
}).then(
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.edit.action?indicateurDictionnaireId=" + id,
}).then(data => {
this.indicateurDictionnaireForm = data;
this.detectedTypeIndicateur = data.typeIndicateur.code;
this.loadIndicateur();
$('#modalIndicateur').modal('show');
})
);
},
resetForm() {
this.indicateurDictionnaireForm = {};
}
}))
})
</script>
模态代码:
<div class="modal" tabindex="-1" role="dialog" id="modalIndicateur">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><s:text name="indicateur.ce.add"/></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form action="<%= request.getContextPath() %>/save.action"
method="POST"
class="w-64 mx-auto" x-data="postForm()" @submit.prevent="submitData">
<div class="form-group" style="padding-left: 15px; padding-bottom: 5px">
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="libelle_fr">
<s:text name="indicateur.ce.libelle_fr"/></label>
<div class="col-md-8">
<input id="libelle_fr" type="text"
name="indicateurDictionnaireForm.libelleFr"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.libelleFr">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="libelle_nl">
<s:text
name="indicateur.ce.libelle_nl"/></label>
<div class="col-md-8">
<input id="libelle_nl" type="text"
name="indicateurDictionnaireForm.libelleNl"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.libelleNl">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="code">
<s:text name="indicateur.ce.code"/></label>
<div class="col-md-8">
<input id="code" type="text"
name="indicateurDictionnaireForm.typeIndicateurCode"
class="form-control input-sm"
x-model="indicateurDictionnaireForm.code">
</div>
</div>
<div class="row">
<label class="control-label col-md-2" style="text-align: left"
for="code">
<s:text name="indicateur.ce.code"/></label>
<div class="col-md-4">
<div class="flex flex-col w-full md:w-2/3">
<select x-model="indicateurDictionnaireForm.typeIndicateurCode"
class="form-control input-sm">
<template x-for="option in typeIndicateurs" x-effect="console.log(detectedTypeIndicateur)">
<option :key="option.code"
:value="option.code"
selected="option.code === detectedTypeIndicateur"
x-effect="console.log('code:', option.code, ' type: ', detectedTypeIndicateur)"
x-text="option.libellefr"></option>
</template>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"
@click="resetForm()">
<s:text name="common.button.quitter"/>
</button>
<button type="submit" class="btn btn-primary">
<s:text name="common.button.save"/>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
使用 x-effect 标签,我尝试记录要在 select 标签中 selected 的检测项目的值和值本身。
这是模式打开时的控制台输出:
// first loop
<empty string>
code: RESULTAT type: <empty string>
code: INCIDENCE type: <empty string>
code: REALISATION type: <empty string>
// second loop
REALISATION
code: RESULTAT type: REALISATION
code: INCIDENCE type: REALISATION
code: REALISATION type: REALISATION
在第一个循环中,我看到函数 editIndicateur() 的值是空的,所以我假设,alpine.s 尝试在回调之前呈现模态,然后进行第二个循环。
您是否知道为什么模态框在值被函数 return 之前填充,以及为什么它会第二次循环?
感谢您的帮助
您将 select
元素绑定到 indicateurDictionnaireForm.typeIndicateurCode
变量,并将其选项绑定到 typeIndicateurs
变量(通过对其进行循环)。
所以当你调用editIndicateur
方法时:
- 首先,您通过 AJAX 调用仅加载 select 选项,然后通过
this.typeIndicateurs = data
将其传递给 Alpine.js。 - Alpine 检测到您更改了
typeIndicateurs
并呈现模板。这是第一个循环。但是此时indicateurDictionnaireForm
或detectedTypeIndicateur
均未设置,因此它们将为空。 - 之后您进行新的 AJAX 调用,在其中加载额外的表单数据并设置
indicateurDictionnaireForm
和detectedTypeIndicateur
变量。 - Alpine.js 检测到您更改了两个附加变量并再次呈现模板。这是第二个循环。
如果您有两个单独的 AJAX 调用,这是正常行为,因为更改不会在同一个更新队列中。要解决此问题,您应该在 editIndicateur
函数内创建一个本地(非反应性)变量,然后在第一次 AJAX 调用时将选项的数组存储在该变量中。然后在第二个 AJAX 调用中将所有 3 个 Alpine.js 反应变量设置在一起,因此更改将在同一个更新队列中,因此 Alpine.js 只渲染模板一次。
editIndicateur(id) {
let indicators;
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.load.type-indicateur.action"
}).then(data => {
indicators = data;
}).then(
$.ajax({
type: "GET",
url: "<%= request.getContextPath() %>/mesures.edit.action?indicateurDictionnaireId=" + id,
}).then(data => {
this.indicateurDictionnaireForm = data;
this.detectedTypeIndicateur = data.typeIndicateur.code;
this.typeIndicateurs = indicators;
this.loadIndicateur();
$('#modalIndicateur').modal('show');
})
);
},