Q1:
我正在尝试创建一个表单,它使用从API获取的初始值填充。每次用户编辑表单中的任何字段时,都应该向API发送POST请求,然后更新初始值。我的当前解决方案可以工作,但是当我在django模板中的for-循环中使用它时,生成的html文件的可读性并不理想,因为脚本重复了很多次。我认为将获取脚本提取到函数将使模板更易读,但我不知道如何更新包含用户在函数表单中给出的所有值的x-data组件的内容。
当前的解决方案如下(样式等不必要地清理):
<form action="/submitcars" method="POST"
x-data="{ dynamic_cars: [] }"
x-init="dynamic_cars = await (await fetch('/dynamic_cars')).json()">
{% csrf_token %}
<div class="row-container">
{% for row in rows %}
{% for i in 0|range:3 %}
<div class="input_container car-{{i}}" x-show="open">
<div class="edited_icon" x-show="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited === 'true'">
<i class="material-icons refresh" @click="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited = 'false'">refresh</i>
</div>
<div class="edited_icon" x-show="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited === 'false'"></div>
<input required
step="any"
id=id_form_car_{{i}}-{{row.row_id}}
name=form_car_{{i}}-{{row.row_id}}
type="{{row.html_type}}"
tabindex="{{i}}"
x-model.lazy="dynamic_cars.cars[{{i}}].{{row.row_id}}.value"
@change= "dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited = 'true',
dynamic_cars = await (
await fetch('/dynamic_cars', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.head.querySelector('meta[name=csrf-token]').content
},
body: JSON.stringify(dynamic_cars)
})).json()"></input>
<div class="unit" x-text="dynamic_cars.cars[{{i}}].{{row.row_id}}.unit"></div>
</div>
{% endfor %}
{% endfor %}
</div>
<div class="submit-button">
<button type="submit" value="submit">Save</button>
</div>
</form>
...
编辑:添加了django循环和变量。row.row_id有一个属性名,它告诉我在CSS网格的每一行上显示的信息(make、model等)。所以这三辆车都有这些特性。Row.row_id与dynamic_cars API响应中的属性名匹配:
cars: [
{
{make: {value: "Toyota", unit: "", user_edited: "false"},
{model: {value: "Camry", unit: "", user_edited: "false"},
...
70+ more properties for each car
...
},
{...},
{...}
]
与其在x-on:change之后进行整个后置提取,不如使用以下内容:
x-on:change="postCars(dynamic_cars)"
<script>
async function postCars(dynamic_cars) {
response = await fetch('/dynamic_cars', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.head.querySelector('meta[name=csrf-token]').content
},
body: JSON.stringify(dynamic_cars)
})
.then(response => {
if(response.ok) return response.json();
})
}
</script>
通过这样做,我可以看到脚本中的POST请求在其有效负载中具有dynamic_cars中的数据,并且响应是正确的,但是如果编辑输入,则dynamic_cars对象被设置为空,并且表单中的初始值消失。应该如何正确地做到这一点?
Q2:
我的另一个问题有点离题,但可能与javascript的基本知识有关,即浏览器开发工具中的控制台显示错误消息:
Alpine Expression Error: Cannot read properties of undefined (reading 'value')
Expression: "dynamic_cars.cars[1].make.value"
AND
Uncaught TypeError: Cannot read properties of undefined (reading '1')
这是否意味着我必须按照API返回的那样在x数据中定义/初始化dynamic_cars?如果API响应非常复杂,并且有大量的数据,甚至是未知的呢?编写x-data="{}“open将意味着数百行javascript,由于当前解决方案的工作方式不同,除了控制台错误之外,我不需要编写和维护这些代码。
发布于 2022-01-18 04:33:34
在控制台中看到这些错误消息的原因是试图将表单字段绑定到不存在的对象。在Alpine.js环境中,只有一个空列表:x-data="{ dynamic_cars: [] }"
。当Alpine.js最初尝试将输入字段绑定到相应的变量(例如dynamic_cars.cars[1].make.value
)时,dynamic_cars
甚至没有cars
属性,实际上,它甚至不是一个对象,而是一个列表,因此也存在类型错误。
在错误的数据绑定周期之后,Alpine.js执行您在x-init
中提供的获取后端并最终更新dynamic_cars
变量的代码,因此现在它有了以前尝试绑定表单字段的属性/字段。令人惊讶的是,它仍然在某种程度上起作用,但IMHO它是相当不明确的行为,应该避免。
但是,由于您知道表单中的项/属性的数量,因此为它们创建正确的JS数据结构非常简单,因此Alpine.js可以将它们绑定到相应的表单字段。在获取后端之后,我们只需用获取的数据更新这个对象,Alpine.js就可以自动更新DOM。
<form action="/submitcars" method="POST" x-data="carsForm" @change="postCars">
<div>
{% for row in rows %}
{% for i in 0|range:3 %}
<div class="input_container car-{{i}}" x-show="open">
<div class="edited_icon" x-show="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited">
<i class="material-icons refresh" @click="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited = false">refresh</i>
</div>
<div class="edited_icon" x-show="dynamic_cars.cars[{{i}}].{{row.row_id}}.user_edited"></div>
<input required
step="any"
id=id_form_car_{{i}}-{{row.row_id}}
name=form_car_{{i}}-{{row.row_id}}
type="{{row.html_type}}"
tabindex="{{i}}"
x-model.lazy="dynamic_cars.cars[{{i}}].{{row.row_id}}.value" />
<div class="unit" x-text="dynamic_cars.cars[{{i}}].{{row.row_id}}.unit"></div>
</div>
{% endfor %}
{% endfor %}
</div>
<div class="submit-button">
<button type="submit" value="submit">Tallenna</button>
</div>
</form>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('carsForm', () => ({
dynamic_cars: {cars: {
{% for i in 0|range:3 %}
{{ i }}: {
{% for row in rows %}
{{ row.row_id }}: {value: '', user_edited: true, unit: ''},
{% endfor %}
},
{% endfor %}
}},
init() {
this.getCars()
},
getCars() {
// ... fetch backend as usual
// Sync new data with local this.dynamic_cars
this.syncData(response.json())
},
syncData(new_data) {
for (let i in new_data.cars) {
let car = new_data.cars[i]
for (let property_name in car) {
for (let prop_attr of ['value', 'unit', 'user_edited']) {
this.dynamic_cars.cars[i][property_name][prop_attr] = car[property_name][prop_attr]
}
}
}
},
postCars() {
// Post to the backend with payload: JSON.stringify(this.dynamic_cars)
// Sync response data with local this.dynamic_cars
this.syncData(response.json())
}
}))
})
</script>
我们的Alpine.js组件名为carsForm
,我们在alpine:init
事件中使用了Alpine.data(),以确保在我们的环境中Alpine.js已经就绪。您可以看到,HTML只包含极小的与Alpan.js相关的属性:x-data
和@change
以及数据绑定属性。
首先,我们创建空的dynamic_cars
Alpine.js变量,该变量具有尽可能多的行/项/属性/等等,因此Alpine.js可以将它们绑定到各自的表单字段。
在从getCars()
执行的init()
方法中,我们获取后端并使用响应数据作为参数调用syncData()
函数。此函数迭代深度嵌套的数据结构,并更新本地dynamic_cars
变量中的相应值。之后,Alpine.js更新DOM。
在用户更新表单字段后,@change
指令调用我们的postCars()
方法,该方法使用有效负载dynamic_cars
将文章发送到后端。当后端响应时,我们再次调用syncData()
,用后端数据的最新版本更新本地dynamic_cars
变量。
注意:user_edited
将被解析,因此您不必进行字符串比较,只需将其用作布尔变量。
https://stackoverflow.com/questions/70740463
复制