前端

Odoo 网站动态 snippet 为什么不是“换个模板拉点数据”:过滤器、模板类名与占位状态链路讲透

很多人做 Odoo 网站动态 snippet 定制时,只盯着模板和接口返回值。可 website 的 dynamic snippet 源码说明,真正稳定的机制是过滤器获取、模板缓存、dataset 默认值、类名切换和 loading/empty 状态协同。本文把这条链路拆开讲清楚。

前端
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人第一次看 Odoo 网站的 dynamic snippet,会把它理解成:

  1. 前端调个接口拿数据;
  2. 选个模板渲染出来;
  3. 结束。

addons/website/static/src/builder/plugins/options/dynamic_snippet_option_plugin.js 说明,官方真正关心的并不是“能不能显示数据”,而是:

  • snippet 刚拖进页面时如何自动补齐默认配置;
  • 过滤器和模板如何缓存;
  • 模板切换时 DOM 与 dataset 如何同步;
  • 单条模式和多条模式怎么分流;
  • 页面编辑态和最终显示态之间如何保持一致。

所以 dynamic snippet 本质上不是“模板 + RPC”,而是一条完整的选项驱动渲染管线

一、dynamic snippet 真正的核心,不是渲染函数,而是 option plugin

很多人下意识会去找最终模板长什么样,或者接口返回什么 JSON。

但从源码看,真正的中枢在 DynamicSnippetOptionPlugin

  • 它负责注册 builder option
  • 提供 builder action
  • 处理 snippet dropped 事件
  • 拉取 filter 和 template
  • 决定默认 dataset
  • 更新模板相关 class / dataset / 容器样式

这说明官方把 dynamic snippet 视作“编辑器里的可配置内容块”,而不是单纯的前台小组件。

换句话说,编辑态的配置机制才是第一主角。

二、为什么 snippet 刚拖进页面时,要先进入 loading / 非 empty 状态

onSnippetDropped() 里有个很值得注意的动作:

  • 如果匹配 dynamic snippet selector,就先 setOptionsDefaultValues
  • 如果带 s_dynamic,就移除 o_dynamic_snippet_empty
  • 并添加 o_dynamic_snippet_loading

这一步很能体现 Odoo 的产品思路。

官方不希望用户刚拖一个动态块进来,就看到一个“什么都没有”的死壳子。它要明确区分三种状态:

  1. empty:还没进入正常动态内容装载语义
  2. loading:正在等待配置或数据
  3. ready:配置和渲染完成

这意味着 dynamic snippet 的状态不是只靠数据接口结果决定,而是由编辑器配置流程一起驱动。

三、默认值不是拍脑袋写死,而是通过 filter / template 双缓存决定

插件里建了两个 Cache:

  • dynamicFiltersCache
  • dynamicFilterTemplatesCache

分别包着:

  • /website/snippet/options_filters
  • /website/snippet/filter_templates

这说明官方把“可选过滤器”和“可用模板”看成两个不同维度的资源。

为什么要拆两层缓存

因为它们变动频率和使用场景不同:

  • filter 更偏业务对象与数据来源
  • template 更偏展示结构与样式能力

如果混成一个大响应包,编辑态的切换和复用都会更重。

拆开后可以更灵活:

  • 换模型时只重取相关模板
  • 同模型下多个 snippet 共享模板缓存
  • 多次打开 option 面板不必重复请求

这不是性能小技巧,而是在明确:

dynamic snippet = 数据选择层 + 展示模板层,两者分离。

四、dataset 才是 snippet 配置的真实载体,不是组件 state

setOptionsDefaultValues()updateTemplate() 大量在改 snippetEl.dataset

  • snippetModel
  • snippetResId
  • templateKey
  • filterId
  • numberOfRecords
  • numberOfElements
  • numberOfElementsSmallDevices
  • extraClasses
  • columnClasses

这背后是一个很重要的前端设计选择:

Odoo 网站 builder 的配置,要落在 DOM 上

为什么不用纯 JS state?因为 snippet 不是普通内存组件,它必须:

  • 在编辑器里可见、可复制、可保存
  • 写回 HTML 结构
  • 在前台渲染时还能继续读出配置
  • 被别的插件、模板、交互逻辑继续消费

所以 dataset 在这里不只是“方便取值”,而是持久化配置协议

这也解释了为什么很多自定义 snippet 二开容易不稳:

  • 只在组件内存里改了值
  • DOM 上没留下可持久化语义
  • 保存后或切换编辑态后状态丢失

五、模板切换不是简单替换 HTML,而是一整套 class / dataset / 容器同步

updateTemplate() 很值得细看。

它做的事情不是“new template render 一下”,而是逐步同步:

  1. 更新 templateKey
  2. 移除旧 template 对应 class
  3. 添加新 template 对应 class
  4. 同步 numberOfElements / numberOfRecords 等 dataset
  5. 更新 container 的 class
  6. 更新 content 的 class
  7. 更新 snippet 根节点的额外 class
  8. dispatch dynamic_snippet_template_updated

这说明官方理解中的“换模板”,不是一块孤立的视图替换,而是:

  • 外层容器可能要变
  • 栅格列数可能要变
  • 获取条数可能要变
  • 根节点语义 class 也可能要变
  • 别的插件还得知道模板变了

所以 dynamic snippet 模板,本质上更接近一个“布局协议包”,而不是单独的 QWeb 片段。

六、为什么要区分 single mode 和 normal mode

插件里有 isSingleModeSnippet()getDefaultSnippetRecordId()snippetResId 等逻辑。

这说明官方明确把两类场景区分开:

1)单记录模式

典型像:

  • 指定某一个产品
  • 指定某一篇博客
  • 指定某一个案例

这时核心配置是:

  • 模型名
  • 具体记录 ID
  • 与单记录匹配的模板

2)多记录模式

典型像:

  • 最新文章列表
  • 推荐产品轮播
  • 某个过滤器下的记录集合

这时核心配置变成:

  • filterId
  • 拉取条数
  • 每屏元素数
  • 模板布局参数

如果这两套逻辑混在一起,你会遇到非常多边界混乱:

  • 选了单条记录却还保留 list filter
  • 模板要求轮播却只有 1 条记录
  • 默认值补齐时不知道该优先 record 还是 filter

官方专门拆 single mode,就是为了避免这类“配置维度不一致”的问题。

七、模板 class 不是装饰,而是从 template key 到 DOM 语义的桥

getTemplateClass(templateKey) 会把模板 key 映射成 snippet class,例如转成 s_* 风格类名。

这一步的价值在于:

  • HTML 结构本身带出当前模板身份;
  • 编辑器、样式层、前台渲染层都能通过 class 感知模板;
  • 即使不重新解析一大坨配置,也能快速判断当前 snippet 采用哪套模板语义。

这说明 class 在这里不是纯视觉样式,而是模板语义标识

八、dispatch 事件说明 dynamic snippet 不是孤立插件

updateTemplate() 最后会 dispatch dynamic_snippet_template_updated

这代表 Odoo 已经预设了一个事实:

模板切换后,别的插件或逻辑可能也需要跟着做事。

这是一种非常健康的扩展设计。

相比把所有后续逻辑写死在一个插件内部,事件分发让别的模块有机会:

  • 响应模板变化
  • 调整附属 UI
  • 做额外校验
  • 做后续重排

这也是 Odoo 前端常见的思路:核心插件负责定义主语义,协同插件靠事件接力。

九、实战里最容易踩的坑

误区 1:只改模板,不改 dataset

这样编辑器里可能暂时看着正常,但保存、刷新或切换选项后状态会乱。

误区 2:把 filter 和 template 当成一个概念

filter 决定“取什么”,template 决定“怎么摆”。两者耦合过死,扩展会非常难受。

误区 3:忽略 loading / empty 状态

一旦异步拉取稍慢,页面就会闪空白、抖布局,用户体验非常差。

误区 4:单记录和多记录共用一套默认值逻辑

这会让 record、filter、count、carousel 参数互相污染。

误区 5:只在编辑器里能跑,前台最终 HTML 却没有保留语义

这种二开短期 demo 很像成功,真正保存发布后就开始崩。

十、结论

Odoo 网站的 dynamic snippet,真正复杂的地方从来不是“调个接口回来渲染一下”。

它更像一条由 option plugin 驱动的前端内容流水线:

  • 先识别 snippet 类型
  • 再补齐 dataset 默认值
  • 拉过滤器与模板资源
  • 区分单条 / 多条模式
  • 切换模板类名与容器结构
  • 用 loading / empty 状态保证体验
  • 再通过事件让协同插件接力

所以更准确的理解是:

dynamic snippet 不是一个小组件,而是一套“可编辑、可持久化、可切换模板”的内容配置协议。

理解这点之后,你再去做文章流、产品流、案例流之类的动态块定制,思路就会稳很多:先设计配置协议,再谈渲染细节。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。