Vuejs html 复选框检查状态未正确更新
Vuejs html checkbox check state not updated correctly
在使某些复选框正常工作时遇到问题。所以在我的组件中,我在状态变量 tokenPermissions
中设置了一组对象,看起来像这样
tokenPermissions: [
{
groupName: "App",
allSelected: false,
someSelected: false,
summary: "Full access to all project operations",
permissions: [
{
name: "can_create_app",
summary: "Create new projects",
selected: false,
},
{
name: "can_delete_app",
summary: "Delete existing projects",
selected: false,
},
{
name: "can_edit_app",
summary: "Edit an existing project",
selected: false,
},
],
}
],
目标是遍历这个数组,并有一个像这样的父复选框和子复选框 tokenPermissions[i].allSelected
绑定到父复选框,对于 tokenPermissions[i].permissions
中的每个对象,一个对应的复选框绑定到 selected
属性 这样 tokenPermissions[i].permissions[j].selected
.
选中父复选框时的期望行为,
- 如果选中所有子复选框,取消选中所有复选框,包括父复选框
- 如果未选中子复选框,请选中所有复选框,包括父复选框
- 如果只有部分子复选框被选中,父复选框将显示不确定的
-
图标或符号,并在单击时取消选中包括父复选框在内的所有子复选框。
问题是第 3 点。问题是有时根据绑定到的属性的状态未正确选中父复选框。例如 allSelected
可以是 false
但父复选框已选中。
我在 github 上放了一个完整的工作示例 https://github.com/oaks-view/vuejs-checkbox-issue。
绑定的部分代码如下
<ul
class="list-group"
v-for="(permissionGroup, permissionGroupIndex) in tokenPermissions"
:key="`${permissionGroup.groupName}_${permissionGroupIndex}`"
>
<li class="list-group-item">
<div class="permission-container">
<div>
<input
type="checkbox"
:indeterminate.prop="
!permissionGroup.allSelected && permissionGroup.someSelected
"
v-model="permissionGroup.allSelected"
:id="permissionGroup.groupName"
v-on:change="
permissionGroupCheckboxChanged($event, permissionGroupIndex)
"
/>
<label :for="permissionGroup.groupName" class="cursor-pointer"
>{{ permissionGroup.groupName }} -
<span style="color: red; margin-left: 14px; padding-right: 3px">{{
permissionGroup.allSelected
}}</span></label
>
</div>
<div class="permission-summary">
{{ permissionGroup.summary }}
</div>
</div>
<ul class="list-group">
<li
class="list-group-item list-group-item-no-margin"
v-for="(permission, permissionIndex) in permissionGroup.permissions"
:key="`${permissionGroup.groupName}_${permission.name}_${permissionIndex}`"
>
<div class="permission-container">
<div>
<input
type="checkbox"
:id="permission.name"
v-bind:checked="permission.selected"
v-on:change="
permissionGroupCheckboxChanged(
$event,
permissionGroupIndex,
permissionIndex
)
"
/>
<label :for="permission.name" class="cursor-pointer"
>{{ permission.name
}}<span
style="color: red; margin-left: 3px; padding-right: 3px"
> {{ permission.selected }}</span
></label
>
</div>
<div class="permission-summary">
{{ permission.summary }}
</div>
</div>
</li>
</ul>
</li>
</ul>
并更新复选框
getPermissionGroupSelectionStatus: function (permissionGroup) {
let allSelected = true;
let someSelected = false;
permissionGroup.permissions.forEach((permission) => {
if (permission.selected === false) {
allSelected = false;
}
if (permission.selected === true) {
someSelected = true;
}
});
return { allSelected, someSelected };
},
permissionGroupCheckboxChanged: function (
$event,
permissionGroupIndex,
permissionIndex
) {
const { checked } = $event.target;
// its single permission selected
if (permissionIndex !== undefined) {
this.tokenPermissions[permissionGroupIndex].permissions[
permissionIndex
].selected = checked;
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
this.tokenPermissions[permissionGroupIndex].allSelected = allSelected;
this.tokenPermissions[permissionGroupIndex].someSelected = someSelected;
} else {
// its selectAll check box
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
let checkAll;
// no checkbox / permission is selected then set all
if (!someSelected && !allSelected) {
checkAll = true;
} else {
checkAll = false;
}
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
}
},
这是一个渲染问题。
Vue 将 allSelected
复选框设置为已选中,然后在同一循环中将其更新为 false;你可以在这里阅读 Vue 生命周期:https://it.vuejs.org/v2/guide/instance.html
解决它的一个非常残酷(但简单)的方法(我不推荐,但它有助于了解正在发生的事情)是延迟更新。
用 this.$nextTick
:
包装方法 permissionGroupCheckboxChanged
的最后一部分
this.$nextTick(() => {
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
})
这样,当您更改值时,引擎会做出相应的反应。
我仍然不推荐它(我认为 nextTick
对理解 Vue 生命周期很有用,但我建议尽可能不要使用它)。
当 checkAll
不为真 permissionGroupCheckboxChanged
时,将 allSelected
设置为 null
而不是 false
的方法不那么残酷(也更简单):
// this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll ? checkAll : null;
// instead of this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
这样道具就战胜了模型(因为模型值变为 null
)。
但是更好的选择(恕我直言)是在 v-for
循环中使用它自己的组件,并将 allSelected
和 someSelected
作为计算属性而不是值绑定到实变量。
通常你不应该将 ui 状态存储为数据,当它可以从真实数据中推断出来时(我可能是错的,因为我不知道你的应用程序,但在你的情况下我怀疑你对单个复选框感兴趣'值,而 allSelected
/someSelected
仅用于 ui).
在使某些复选框正常工作时遇到问题。所以在我的组件中,我在状态变量 tokenPermissions
中设置了一组对象,看起来像这样
tokenPermissions: [
{
groupName: "App",
allSelected: false,
someSelected: false,
summary: "Full access to all project operations",
permissions: [
{
name: "can_create_app",
summary: "Create new projects",
selected: false,
},
{
name: "can_delete_app",
summary: "Delete existing projects",
selected: false,
},
{
name: "can_edit_app",
summary: "Edit an existing project",
selected: false,
},
],
}
],
目标是遍历这个数组,并有一个像这样的父复选框和子复选框 tokenPermissions[i].allSelected
绑定到父复选框,对于 tokenPermissions[i].permissions
中的每个对象,一个对应的复选框绑定到 selected
属性 这样 tokenPermissions[i].permissions[j].selected
.
选中父复选框时的期望行为,
- 如果选中所有子复选框,取消选中所有复选框,包括父复选框
- 如果未选中子复选框,请选中所有复选框,包括父复选框
- 如果只有部分子复选框被选中,父复选框将显示不确定的
-
图标或符号,并在单击时取消选中包括父复选框在内的所有子复选框。
问题是第 3 点。问题是有时根据绑定到的属性的状态未正确选中父复选框。例如 allSelected
可以是 false
但父复选框已选中。
我在 github 上放了一个完整的工作示例 https://github.com/oaks-view/vuejs-checkbox-issue。
绑定的部分代码如下
<ul
class="list-group"
v-for="(permissionGroup, permissionGroupIndex) in tokenPermissions"
:key="`${permissionGroup.groupName}_${permissionGroupIndex}`"
>
<li class="list-group-item">
<div class="permission-container">
<div>
<input
type="checkbox"
:indeterminate.prop="
!permissionGroup.allSelected && permissionGroup.someSelected
"
v-model="permissionGroup.allSelected"
:id="permissionGroup.groupName"
v-on:change="
permissionGroupCheckboxChanged($event, permissionGroupIndex)
"
/>
<label :for="permissionGroup.groupName" class="cursor-pointer"
>{{ permissionGroup.groupName }} -
<span style="color: red; margin-left: 14px; padding-right: 3px">{{
permissionGroup.allSelected
}}</span></label
>
</div>
<div class="permission-summary">
{{ permissionGroup.summary }}
</div>
</div>
<ul class="list-group">
<li
class="list-group-item list-group-item-no-margin"
v-for="(permission, permissionIndex) in permissionGroup.permissions"
:key="`${permissionGroup.groupName}_${permission.name}_${permissionIndex}`"
>
<div class="permission-container">
<div>
<input
type="checkbox"
:id="permission.name"
v-bind:checked="permission.selected"
v-on:change="
permissionGroupCheckboxChanged(
$event,
permissionGroupIndex,
permissionIndex
)
"
/>
<label :for="permission.name" class="cursor-pointer"
>{{ permission.name
}}<span
style="color: red; margin-left: 3px; padding-right: 3px"
> {{ permission.selected }}</span
></label
>
</div>
<div class="permission-summary">
{{ permission.summary }}
</div>
</div>
</li>
</ul>
</li>
</ul>
并更新复选框
getPermissionGroupSelectionStatus: function (permissionGroup) {
let allSelected = true;
let someSelected = false;
permissionGroup.permissions.forEach((permission) => {
if (permission.selected === false) {
allSelected = false;
}
if (permission.selected === true) {
someSelected = true;
}
});
return { allSelected, someSelected };
},
permissionGroupCheckboxChanged: function (
$event,
permissionGroupIndex,
permissionIndex
) {
const { checked } = $event.target;
// its single permission selected
if (permissionIndex !== undefined) {
this.tokenPermissions[permissionGroupIndex].permissions[
permissionIndex
].selected = checked;
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
this.tokenPermissions[permissionGroupIndex].allSelected = allSelected;
this.tokenPermissions[permissionGroupIndex].someSelected = someSelected;
} else {
// its selectAll check box
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
let checkAll;
// no checkbox / permission is selected then set all
if (!someSelected && !allSelected) {
checkAll = true;
} else {
checkAll = false;
}
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
}
},
这是一个渲染问题。
Vue 将 allSelected
复选框设置为已选中,然后在同一循环中将其更新为 false;你可以在这里阅读 Vue 生命周期:https://it.vuejs.org/v2/guide/instance.html
解决它的一个非常残酷(但简单)的方法(我不推荐,但它有助于了解正在发生的事情)是延迟更新。
用 this.$nextTick
:
permissionGroupCheckboxChanged
的最后一部分
this.$nextTick(() => {
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
})
这样,当您更改值时,引擎会做出相应的反应。
我仍然不推荐它(我认为 nextTick
对理解 Vue 生命周期很有用,但我建议尽可能不要使用它)。
当 checkAll
不为真 permissionGroupCheckboxChanged
时,将 allSelected
设置为 null
而不是 false
的方法不那么残酷(也更简单):
// this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll ? checkAll : null;
// instead of this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
这样道具就战胜了模型(因为模型值变为 null
)。
但是更好的选择(恕我直言)是在 v-for
循环中使用它自己的组件,并将 allSelected
和 someSelected
作为计算属性而不是值绑定到实变量。
通常你不应该将 ui 状态存储为数据,当它可以从真实数据中推断出来时(我可能是错的,因为我不知道你的应用程序,但在你的情况下我怀疑你对单个复选框感兴趣'值,而 allSelected
/someSelected
仅用于 ui).