Atom的view框架SpacePen

虽然现在SpacePen已经不被官方支持了,但是在Atom团队退出新的view系统之前,他还是最好的选择。

例子

space-pen定义了一套生成HTML的DSL,只要继承View类,并且写一个content类方法,就可以在这个类方法中使用这套DSL:

class Spacecraft extends View
  @content: ->
    @div =>
      @h1 "Spacecraft"
      @ol =>
        @li "Apollo"
        @li "Soyuz"
        @li "Space Shuttle"

这个DSL还是比较明义的,有div,h1,ol这些方法加上参数构成。

View继承自jQuery,所以View子类的实力上可以使用所有jQuery方法:

view = new Spacecraft
view.find('ol').append('<li>Star Destroyer</li>')

view.on 'click', 'li', ->
  alert "They clicked on #{$(this).text()}"

原理

SpacePen的View是JQuery的子类。SpacePen的任务就是根据content函数中的定义构造出一个对应的JQuery对象。

class View extends jQuery

SpacePen的构造函数如下:

constructor: (args...) ->
  if @element?
    //如果设置了element,则用element实例化。这里调用jQuery.fn.init.call,可以理解为“调用父类构造函数”
    jQuery.fn.init.call(this, @element)
  else
    //否则使用子类定义的content函数来生成HTML,并实例化
    [html, postProcessingSteps] = @constructor.buildHtml -> @content(args...)
    jQuery.fn.init.call(this, html)
    throw new Error("View markup must have a single root element") if @length != 1
    @element = @[0]
    @element.attached = => @attached?()
    @element.detached = => @detached?()

  //处理outlet标签
  @wireOutlets(this)
  //处理事件标签
  @bindEventHandlers(this)

  //在所有子元素上添加spacePenView属性,设置为this
  @element.spacePenView = this
  treeWalker = document.createTreeWalker(@element, NodeFilter.SHOW_ELEMENT)
  while element = treeWalker.nextNode()
    element.spacePenView = this

  //触发后置处理步骤(subview是在这个步骤才添加上的)
  if postProcessingSteps?
    step(this) for step in postProcessingSteps

  //如果定义了额外的初始化函数,则调用
  @initialize?(args...)

最为核心的是buildHtml,也就是生成这个View的HTML。这是通过Builder这个类实现的。代码不长:

class Builder
  constructor: ->
    @document = []
    @postProcessingSteps = []

  buildHtml: ->
    [@document.join(''), @postProcessingSteps]

  //@div@xxx什么的其实是转化到调用这个方法,name就是"div"tag: (name, args...) ->
    options = @extractOptions(args)

    @openTag(name, options.attributes)

    if SelfClosingTags.hasOwnProperty(name)
      if options.text? or options.content?
        throw new Error("Self-closing tag #{name} cannot have text or content")
    else
      options.content?()
      @text(options.text) if options.text
      @closeTag(name)

  openTag: (name, attributes) ->
    if @document.length is 0
      attributes ?= {}
      attributes.is ?= registerElement(name)

    attributePairs =
      for attributeName, value of attributes
        "#{attributeName}=\"#{value}\""

    attributesString =
      if attributePairs.length
        " " + attributePairs.join(" ")
      else
        ""

    @document.push "<#{name}#{attributesString}>"

  closeTag: (name) ->
    @document.push "</#{name}>"

  text: (string) ->
    escapedString = string
      .replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')

    @document.push escapedString

  raw: (string) ->
    @document.push string

  subview: (outletName, subview) ->
    subviewId = "subview-#{++idCounter}"
    @tag 'div', id: subviewId
    @postProcessingSteps.push (view) ->
      view[outletName] = subview
      subview.parentView = view
      view.find("div##{subviewId}").replaceWith(subview)

  extractOptions: (args) ->
    options = {}
    for arg in args
      switch typeof(arg)
        when 'function'
          options.content = arg
        when 'string', 'number'
          options.text = arg.toString()
        else
          options.attributes = arg
    options

总体上,Builder使用了递归的思想来生成HTML。

技巧

把确定/显示/隐藏的逻辑放在View里

space-pen只是一个view系统,负责输出HTML,怎么用她不管。但是如果我们实现的是一个对话框,可以吧显示/隐藏的逻辑放在View类中,方便使用。DEMO代码如下(参考了md-writer插件):

//初始化,绑定默认快捷键
initialize: ->
  atom.commands.add @element,
    "core:confirm": => @onConfirm()
    "core:cancel": => @detach()

//确认
onConfirm: ->
  ...

//显示
display: ->
  @panel ?= atom.workspace.addModalPanel(item: this, visible: false)
  @previouslyFocusedElement = $(document.activeElement)
  @panel.show()
  ...

//隐藏
detach: ->
  return unless @panel.isVisible()
  @panel.hide()
  @previouslyFocusedElement?.focus()
  super

需要注意的就是,显示前获取当前激活的元素,对话框结束后,恢复之前激活的元素。

关于对话框设计的思想

看了一些插件的源码,对话框类设计的思想主要有两种:

  • 逻辑在对话框类中
  • 逻辑在对话框外,通过回调的方式,对话框来触发

目的性很强的对话框,可以使用第一种。通用的,则用第二种。

参考网址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值