我们在 Day21 提到 data-remote=true
、Day25 提到了一些与Ajax
相关的例子,今天为正式的介绍在Rails
如何完美的搭配Stimulus & Rails
。今天我们会用3个情境教导读者如何使用Stimulus Ajax
相依性的下拉式表单为标准以非同步处理的标准模型之一。以下的操作为先选品牌,当选品牌的同时,右方的下拉选单的选项会随着品牌的不同而有改变
⭐️ 下列为相依性表单的Slim & Stimulus Code
= card(controller: 'admin--blogs') do
= card_header(title: 'Stimulus Ajax')
= card_body do
= tag.div class: 'mx-1 d-flex' do
= datatable_select_tag Brand.all.pluck(:title, :id).prepend(['请选择品牌', nil]),
'brand-select', controller: 'admin--blogs',
target: 'brand', action: 'fetchStores',
style: 'max-width: 300px'
= datatable_select_tag [['请选择店舖', nil]], 'store-select',
controller: 'admin--blogs', target: 'store',
style: 'max-width: 300px'
import { isEmpty, isNil, map, prepend, equals } from 'ramda';
import { Controller } from 'stimulus';
import Rails from '@rails/ujs';
/* 下拉式选单的选项 */
const selectOption = ({ value, text, select = null }) => `<option value=${value} ${equals(select, true) ? 'selected' : ''}>${text}</option>`
const fetchStores = ({ brandId, storeComp }) => {
if(isEmpty(brandId)) {
storeComp.innerHTML = selectOption({ value: '', text: '请选择店舖' })
return
}
Rails.ajax({
type: 'get',
url: `/admin/brands/${brandId}/get_stores`,
success: (response) => {
/* 选单内容 */
const resContent = prepend({ value: '', text: '请选择店舖'}, response)
/* 找不到店柜的下拉式选单 => 跳出 */
if (isNil(storeComp)) return
/* Ajax 内容 */
storeComp.innerHTML =
`${map((e) => selectOption({ value: e.value,
text: e.text }),
resContent).join('')}`
},
error: (error) => {
console.log('error response:', error);
}
})
}
export default class extends Controller {
static targets = ['brand', 'store']
// 依照厂牌取得店舖
fetchStores() {
fetchStores({ brandId: this.brandTarget.value,
storeComp: this.storeTarget, prependWording: '请选择店舖'});
}
}
可以看到打非同步的地方网址为 ⬇️
`/admin/brands/${brandId}/get_stores`
⭐️ 上面的网址列,对应的routes
, controller
分别如下
resources :brands do
get :get_stores, on: :member
end
class Admin::BrandsController < Admin::ApplicationController
# 下拉式选单的 Ajax
def get_stores
@stores = Brand.find_by_id(params[:id])&.stores
render json: @stores&.map { |s| { value: s.id, text: s.title_zh } }
end
end
⭐️ 透过非同步的动作取得的成功回应为底下的response
,并且非同步回传的结果前方加上{ value: '', text: '请选择店舖'}
,并且使用selectOption
组成下拉式选单的DOM
,成为了搭配非同步处理的相依性选单。
import Rails from '@rails/ujs';
Rails.ajax({
type: 'get',
url: `/admin/brands/${brandId}/get_stores`,
success: (response) => {
/* 选单内容 */
const resContent = prepend({ value: '', text: '请选择店舖'}, response)
/* 找不到店柜的下拉式选单 => 跳出 */
if (isNil(storeComp)) return
/* Ajax 内容 */
storeComp.innerHTML =
`${map((e) => selectOption({ value: e.value,
text: e.text }),
resContent).join('')}`
},
error: (error) => {
console.log('error response:', error);
}
})
还记得Day26的greet()
吗? 当时我们使用greet()
来触发简单的JS动作,而我们可以透过事件触发非同步的动作,接着我们要来介绍,如何使用 Ajax 处理非同步的问题,以下为呈现的结果。
刚刚提到,只要打非同步就会需要打到後端,就需要事先设定并打通routes
, controller
,因此我们先将讲路径和逻辑写出来
⭐️ 下列为routes
resources :blogs do
#====== ajax
post :search, on: :collection
end
⭐️ 下列为 controller
, view
。顺带一提,render partial
的写法不只是view
的专利,我们也可以在controller
写。
module Admin
class BlogsController < ApplicationController
def search
blog = Blog.find_by_id params[:id]
render partial: 'searched_blog', locals: { blog: blog }
end
end
end
⭐️ 在 app/views/admin/blogs/_searched_blog
中写下欲渲染的画面
br
= log_template do
= log_item title: 'id' do
= blog&.id || ''
= log_item title: '标题' do
= blog&.title || '找不到标题'
= log_item title: '内文' do
= blog&.content || '找不到内文'
⭐️ 由於想要用实际的例子让读者感受使用Value的用法,我们将路径
放在Value当中
= card(controller: 'admin--blogs', data: { 'admin--blogs-query-url-value': search_admin_blogs_path }) do
= card_header(title: 'Stimulus Ajax')
= card_body do
// 内容省略......
= tag.div class: 'mx-2 my-3 p-2', style: 'width: 300px; border: 1px solid black' do
= tag.div(class: 'form-group string required admin_blogs_search_id')
= tag.label tag.strong('搜寻id')
= tag.input name: nil, class: "form-control string required",
data: { 'admin--blogs-target': 'ajaxId' }
= button_tag '搜寻', type: 'button', data: { action: 'admin--blogs#ajaxGreet' },
class: 'btn btn-primary'
= tag.div data: { 'admin--blogs-target': 'searchedContent' }
import { isEmpty, isNil, map, prepend, equals } from 'ramda';
import { Controller } from 'stimulus';
import Rails from '@rails/ujs';
/* 下拉式选单的选项 */
const selectOption = ({ value, text, select = null }) =>
`<option value=${value} ${equals(select, true) ? 'selected' : ''}>${text}</option>`
const fetchStores = ({ brandId, storeComp }) => {
if(isEmpty(brandId)) {
storeComp.innerHTML = selectOption({ value: '', text: '请选择店舖' })
return
}
Rails.ajax({
type: 'get',
url: `/admin/brands/${brandId}/get_stores`,
success: (response) => {
/* 选单内容 */
const resContent = prepend({ value: '', text: '请选择店舖'}, response)
/* 找不到店柜的下拉式选单 => 跳出 */
if (isNil(storeComp)) return
/* Ajax 内容 */
storeComp.innerHTML =
`${map((e) => selectOption({ value: e.value,
text: e.text }),
resContent).join('')}`
},
error: (error) => {
console.log('error response:', error);
}
})
}
export default class extends Controller {
static targets = ["searchedContent", 'ajaxId']
static values = { queryUrl: String }
connect() {
console.log('this.queryUrlValue', this.queryUrlValue)
}
ajaxGreet() {
Rails.ajax({
type: 'post',
url: this.queryUrlValue,
data: new URLSearchParams({
id: this.ajaxIdTarget.value
}),
success: (data, status, xhr) => {
this.searchedContentTarget.innerHTML = xhr.response;
},
error: (error) => {
console.log('error response:', error);
}
})
}
}
我们将後端传过来的search_admin_blogs_path
,传到 this.queryUrlValue
给Rails.ajax
,并且将参数 this.ajaxIdTarget.value
传进 Controller,并且将结果打回来显示在this.searchedContentTarget
。
写好之後,就可以使用了
过往我们常用js.erb
渲染非同步的表单,而当我们引入了Stimulus 以後,我们可以不用重新开一个js.erb
的档案。
⭐️ 此例跟上例用的是一样的routes
, controller
,与上例的情境相同,只不过这边是使用Ajax
送出表单的方式进行非同步,因此使用到data-remote=true
的helper
。
import { Controller } from 'stimulus';
export default class extends Controller {
static targets = ["searchedContent"]
onBlogSuccess(event) {
let [data, status, xhr] = event.detail;
this.searchedContentTarget.innerHTML = xhr.response;
}
onBlogError(event) {
let [data, status, xhr] = event.detail;
console.log(xhr.response);
}
}
= tag.div data: { controller: 'admin--blogs' } do
/ 中间内容省略...
= modal(id: 'new-blog-modal', confirm_wording: '送出文章',
confirm_form: 'new_modal', title: '新增文章') do
/ 中间内容省略...
= simple_form_for(@blog, url: search_admin_blogs_path, method: :post,
html: { data: { remote: true,
action: "ajax:success->admin--blogs#onBlogSuccess
ajax:error->admin--blogs#onBlogError" },
id: 'form2' }) do
= tag.div style: "border: 1px solid black"
= tag.div(class: 'form-group string required admin_blogs_search_id"')
= tag.label tag.strong('搜寻id')
= tag.input name: 'id', class: "form-control string required", form: 'form2'
= submit_tag "搜寻", class: "btn btn-primary btn-sm", form: 'form2',
data: { disable_with: '载入中...' }
= tag.div data: { 'admin--blogs-target': 'searchedContent' }
我们在Day25 提过下列的例子,但Day25的重点是讲述表单包覆表单
的问题,今天的重点在於Ajax
资料的打法。
对於上述的程序码,我们来进行解析
在表单加入remote: true
,代表该表单需要做非同步的处理
ajax:success->admin--blogs#onBlogSuccess
➡️ 当非同步进入成功阶段的反应
ajax:error->admin--blogs#onBlogError
➡️ 当非同步进入失败阶段的反应
其中onBlogSuccess
, onBlogError
分别为在Stimulus自定义的两个动作。
今天介绍了三种Stimulus
搭配非同步表单的例子,还在使用React
, Vue
的朋友们,汉汉老师想要告诉你们,Stimulus
这种基於SSR
的框架很棒,一点也不逊於主流框架,并且目前在社群上已经有很多星星数不多,但实际上已经很好用的套件。
这几天不断地写文章,因此运动跟作息都变得比较不规律,偶尔会写到怀疑人生。写着写着,发现自己想要分享的内容比预期的还要多很多,但总觉得时间不够、文笔不够、实力不够,因此我们会在最後一天写下遗珠之憾,为下次的IT铁人赛做引言。
Rails 真的超棒!希望大家能够认识Rails
今天除了写了Day27以外,还回头顺Day1-5
的文章。
<<: 每个人都该学的30个Python技巧|技巧 27:常用的字串函式统整(字幕、衬乐、练习)
191. Number of 1 Bits 今天这一题是有关於二进制的概念和其与十进位之间的转换,如...
访问控制机制 通常通过三种机制来管理或控制访问:身份验证,授权和会计(AAA)。 .身份验证是“验...
Flutter API Get using Bloc state management and ht...
1.express-session设定 var express = require('express...
小试身手解答: 点击File→Save As... 跳出此视窗,将场景命名为ARVideo储存在此 ...