Atom的view框架SpacePen

来源:互联网 发布:余弦相似度 网络 编辑:程序博客网 时间:2024/06/06 04:59

虽然现在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 Spacecraftview.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

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

关于对话框设计的思想

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

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

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

参考网址

  • atom-archive/space-pen
  • Let’s choose a view framework for docs, generated packages, etc. · Issue #5756 · atom/atom
0 0