DOMite.st 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. Smalltalk createPackage: 'DOMite'!
  2. ProtoStream subclass: #Domite
  3. instanceVariableNames: 'element reference'
  4. package: 'DOMite'!
  5. !Domite commentStamp!
  6. I am (hopefully thin) wrapper around the notion of "cursor in a page".
  7. I represent a DOM node _and_ a point where
  8. to insert new content into it.
  9. So I play both the role of a container that inserts
  10. as well as the role of an element being inserted.
  11. I inherit from `ProtoStream`.
  12. Creation API:
  13. - `Domite new` creates an insertion point at the bottom of `<body>`.
  14. - `Domite newStream` is unique way to create pieces of content. It creates an instance "floating in thin air" (wrapper around DOM DocumentFragment) that can be filled with any contents and then inserted in a page.
  15. - `Domite fromElement: aDomElement` wraps an element and set the cursor to its end.
  16. CSS selector API:
  17. - `Domite at: aSelector` wraps an element found by `document.querySelector(aSelector)`.
  18. Manipulation API:
  19. - `aDomite << obj` inserts obj at the insertion point.
  20. - `aDomite resetContents` deletes contents of the wrapped element.
  21. Cursor moving API:
  22. Take this sample HTML, where `[n]` are just markers, not real content:
  23. ```
  24. <body>
  25. <h1>header</h1>
  26. [4]<p>[2]Hello[1]world[3]</p>[5]
  27. <small>footer</small>
  28. </body>
  29. ```
  30. If `d` is a `Domite` representing `[1]`, then:
  31. - `d setToStart` would move `d` to be at `[2]`,
  32. - `d setToEnd` would move `d` to be at `[3]`,
  33. - `d setToBefore` would move `d` to be at `[4]`, and
  34. - `d setToAfter` would move `d` to be at `[5]`.
  35. It is not presumed one would use `setToXxx`
  36. to actually move around in a single instance.
  37. It is envisioned this API will be used mostly
  38. in combination with `copy`, like
  39. `afterMe := self copy setToAfter`.!
  40. !Domite methodsFor: 'accessing'!
  41. at: aString
  42. ^ self class fromElement: (self element querySelector: aString)
  43. !
  44. attrAt: aString
  45. (element hasAttribute: aString)
  46. ifTrue: [ ^ element getAttribute: aString ]
  47. ifFalse: [ ^ nil ]
  48. !
  49. attrAt: aString put: anotherString
  50. element setAttribute: aString to: anotherString
  51. !
  52. element
  53. ^ element
  54. !
  55. element: anObject
  56. element := anObject
  57. !
  58. propAt: aString
  59. ^ element at: aString
  60. !
  61. propAt: aString put: anObject
  62. ^ element at: aString put: anObject
  63. !
  64. reference
  65. ^ reference
  66. !
  67. reference: anObject
  68. reference := anObject
  69. ! !
  70. !Domite methodsFor: 'comparing'!
  71. = aDomite
  72. ^ self class = aDomite class and: [
  73. self element = aDomite element and: [
  74. self reference = aDomite reference ]]
  75. ! !
  76. !Domite methodsFor: 'converting'!
  77. asJQuery
  78. ^ self element asJQuery
  79. ! !
  80. !Domite methodsFor: 'deletion'!
  81. resetContents
  82. <
  83. var element = self['@element'], child;
  84. while (child = element.firstChild) element.removeChild(child);
  85. self['@reference'] = null;
  86. >
  87. ! !
  88. !Domite methodsFor: 'events'!
  89. off: aString unbind: aBlock
  90. self element removeEventListener: aString block: aBlock useCapture: false
  91. !
  92. on: aString bind: aBlock
  93. self element addEventListener: aString block: aBlock useCapture: false
  94. ! !
  95. !Domite methodsFor: 'initialization'!
  96. initialize
  97. super initialize.
  98. element := document body.
  99. reference := nil asJSON
  100. ! !
  101. !Domite methodsFor: 'insertion'!
  102. nextPut: anObject
  103. self nextPutString: anObject printString
  104. !
  105. nextPutDomNode: aDomElement
  106. self element
  107. insertBefore: aDomElement
  108. reference: self reference
  109. !
  110. nextPutJSObject: aJSObject
  111. (Domite isDomNode: aJSObject)
  112. ifTrue: [ self nextPutDomNode: aJSObject ]
  113. ifFalse: [ self nextPut: aJSObject ]
  114. !
  115. nextPutString: aString
  116. self nextPutDomNode: (
  117. document createTextNode: aString asString )
  118. ! !
  119. !Domite methodsFor: 'positioning'!
  120. reset
  121. self reference: self element firstChild
  122. !
  123. setToAfter
  124. self
  125. reference: self element nextSibling;
  126. element: self element parentNode
  127. !
  128. setToBefore
  129. self
  130. reference: self element;
  131. element: self element parentNode
  132. !
  133. setToEnd
  134. self reference: nil asJSON "null"
  135. !
  136. setToNext
  137. self atEnd ifFalse: [
  138. self reference: self reference nextSibling ]
  139. !
  140. setToPrev
  141. self atStart ifFalse: [
  142. self reference: (self reference
  143. ifNil: [ self element lastChild ]
  144. ifNotNil: [ :ref | ref previousSibling ])]
  145. ! !
  146. !Domite methodsFor: 'reading'!
  147. next
  148. self atEnd
  149. ifTrue: [ ^ nil ]
  150. ifFalse: [ | result |
  151. result := self peek.
  152. self setToNext.
  153. ^ result ]
  154. !
  155. peek
  156. ^ self reference
  157. ifNotNil: [ :ref | self class fromElement: ref ]
  158. !
  159. prev
  160. self atStart
  161. ifTrue: [ ^ nil ]
  162. ifFalse: [ self setToPrev. ^ self peek ]
  163. ! !
  164. !Domite methodsFor: 'streaming'!
  165. putOn: aStream
  166. aStream nextPutDomNode: self element
  167. ! !
  168. !Domite methodsFor: 'testing'!
  169. atEnd
  170. ^ self reference isNil
  171. !
  172. atStart
  173. ^ self reference = self element firstChild
  174. !
  175. canSetToUpperLevel
  176. ^ self element parentNode notNil
  177. !
  178. isInvalid
  179. ^ self element isNil
  180. ! !
  181. !Domite class methodsFor: 'instance creation'!
  182. at: aString
  183. ^ self fromElement: (document querySelector: aString)
  184. !
  185. fromElement: aDomElement
  186. aDomElement ifNotNil: [
  187. (self isDomNode: aDomElement) ifFalse: [
  188. self error: self name, ': Need a DOM node' ]].
  189. ^ self new
  190. element: aDomElement;
  191. yourself
  192. !
  193. fromElement: aDomElement cursorBefore: anotherDomElement
  194. aDomElement ifNotNil: [
  195. (self isDomNode: aDomElement) ifFalse: [
  196. self error: self name, ': Need a DOM node' ]].
  197. ^ self new
  198. element: aDomElement;
  199. referenceElement: anotherDomElement;
  200. yourself
  201. !
  202. newElement: aString
  203. ^ self fromElement: (document createElement: aString)
  204. !
  205. newElement: aString xmlns: anotherString
  206. ^ self fromElement: (document createElementNS: anotherString tagName: aString)
  207. !
  208. newStream
  209. ^ self fromElement: document createDocumentFragment
  210. ! !
  211. !Domite class methodsFor: 'testing'!
  212. isDomNode: anObject
  213. <
  214. return anObject.nodeType >> 0 &&
  215. Object.prototype.toString.call(anObject) !!== "[object Object]"
  216. >
  217. ! !
  218. !ProtoStream methodsFor: '*DOMite'!
  219. nextPutDomNode: aNode
  220. self nextPut: aNode
  221. ! !