DOMite.st 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. - `aDomite at: aSelector` wraps an element found by `element.querySelector(aSelector)`.
  19. - `Domite allAt: aSelector` return collection of wrapped results of `document.querySelectorAll(aSelector)`.
  20. - `aDomite allAt: aSelector` return collection of wrapped results of `element.querySelectorAll(aSelector)`.
  21. Manipulation API:
  22. - `aDomite << obj` inserts obj at the insertion point.
  23. - `aDomite resetContents` deletes contents of the wrapped element.
  24. - `aDomite cutUpTo: anotherDomite` removes contents between the two cursors (or up to the end of the receiver) and returns it collected in a wrapped DocumentFragment (IOW, you can `anotherPlace << theResult` to move the contents in the specified range).
  25. Cursor moving API:
  26. Take this sample HTML, where `[n]` are just markers, not real content:
  27. ```
  28. <body>
  29. <h1>header</h1>
  30. [4]<p>[2]Hello[1]world[3]</p>[5]
  31. <small>footer</small>
  32. </body>
  33. ```
  34. If `d` is a `Domite` representing `[1]`, then:
  35. - `d setToStart` would move `d` to be at `[2]`,
  36. - `d setToEnd` would move `d` to be at `[3]`,
  37. - `d setToBefore` would move `d` to be at `[4]`, and
  38. - `d setToAfter` would move `d` to be at `[5]`.
  39. It is not presumed one would use `setToXxx`
  40. to actually move around in a single instance.
  41. It is envisioned this API will be used mostly
  42. in combination with `copy`, like
  43. `afterMe := self copy setToAfter`.!
  44. !Domite methodsFor: 'accessing'!
  45. allAt: aString
  46. ^ self class wrap: (self class domListAsArray: (self element querySelectorAll: aString))
  47. !
  48. at: aString
  49. ^ self class fromElement: (self element querySelector: aString)
  50. !
  51. attrAt: aString
  52. (element hasAttribute: aString)
  53. ifTrue: [ ^ element getAttribute: aString ]
  54. ifFalse: [ ^ nil ]
  55. !
  56. attrAt: aString put: anotherString
  57. element setAttribute: aString to: anotherString
  58. !
  59. element
  60. ^ element
  61. !
  62. element: anObject
  63. element := anObject
  64. !
  65. propAt: aString
  66. ^ element at: aString
  67. !
  68. propAt: aString put: anObject
  69. ^ element at: aString put: anObject
  70. !
  71. reference
  72. ^ reference
  73. !
  74. reference: anObject
  75. reference := anObject
  76. ! !
  77. !Domite methodsFor: 'comparing'!
  78. = aDomite
  79. ^ self class = aDomite class and: [
  80. self element = aDomite element and: [
  81. self reference = aDomite reference ]]
  82. ! !
  83. !Domite methodsFor: 'converting'!
  84. asJQuery
  85. ^ self element asJQuery
  86. ! !
  87. !Domite methodsFor: 'deletion'!
  88. cutUpTo: aDomite
  89. <
  90. var result = document.createDocumentFragment(),
  91. start = self['@reference'],
  92. end = aDomite['@reference'],
  93. tmp;
  94. while (start && start !!= end) {
  95. tmp = start;
  96. start = start.nextSibling;
  97. result.appendChild(tmp);
  98. }
  99. self['@reference'] = start;
  100. return self._class()._fromElement_(result);
  101. >
  102. !
  103. resetContents
  104. <
  105. var element = self['@element'], child;
  106. while (child = element.firstChild) element.removeChild(child);
  107. self['@reference'] = null;
  108. >
  109. ! !
  110. !Domite methodsFor: 'events'!
  111. fire: aString
  112. self fire: aString detail: nil asJSON
  113. !
  114. fire: aString detail: anObject
  115. self fireEvent: (
  116. self class createCustomEvent: aString detail: anObject)
  117. !
  118. fireEvent: anEvent
  119. self element dispatchEvent: anEvent
  120. !
  121. off: aString unbind: aBlock
  122. self element removeEventListener: aString block: aBlock useCapture: false
  123. !
  124. on: aString bind: aBlock
  125. self element addEventListener: aString block: aBlock useCapture: false
  126. ! !
  127. !Domite methodsFor: 'initialization'!
  128. initialize
  129. super initialize.
  130. element := document body.
  131. reference := nil asJSON
  132. ! !
  133. !Domite methodsFor: 'insertion'!
  134. nextPut: anObject
  135. self nextPutString: anObject printString
  136. !
  137. nextPutDomNode: aDomElement
  138. self element
  139. insertBefore: aDomElement
  140. reference: self reference
  141. !
  142. nextPutJSObject: aJSObject
  143. (self class isDomNode: aJSObject)
  144. ifTrue: [ self nextPutDomNode: aJSObject ]
  145. ifFalse: [ self nextPut: aJSObject ]
  146. !
  147. nextPutString: aString
  148. self nextPutDomNode: (
  149. document createTextNode: aString asString )
  150. ! !
  151. !Domite methodsFor: 'positioning'!
  152. reset
  153. self reference: self element firstChild
  154. !
  155. setToAfter
  156. self
  157. reference: self element nextSibling;
  158. element: self element parentNode
  159. !
  160. setToBefore
  161. self
  162. reference: self element;
  163. element: self element parentNode
  164. !
  165. setToEnd
  166. self reference: nil asJSON "null"
  167. !
  168. setToNext
  169. self atEnd ifFalse: [
  170. self reference: self reference nextSibling ]
  171. !
  172. setToPrev
  173. self atStart ifFalse: [
  174. self reference: (self reference
  175. ifNil: [ self element lastChild ]
  176. ifNotNil: [ :ref | ref previousSibling ])]
  177. ! !
  178. !Domite methodsFor: 'reading'!
  179. next
  180. self atEnd
  181. ifTrue: [ ^ nil ]
  182. ifFalse: [ | result |
  183. result := self peek.
  184. self setToNext.
  185. ^ result ]
  186. !
  187. peek
  188. ^ self reference
  189. ifNotNil: [ :ref | self class fromElement: ref ]
  190. !
  191. prev
  192. self atStart
  193. ifTrue: [ ^ nil ]
  194. ifFalse: [ self setToPrev. ^ self peek ]
  195. ! !
  196. !Domite methodsFor: 'streaming'!
  197. putOn: aStream
  198. aStream nextPutDomNode: self element
  199. ! !
  200. !Domite methodsFor: 'testing'!
  201. atEnd
  202. ^ self reference isNil
  203. !
  204. atStart
  205. ^ self reference = self element firstChild
  206. !
  207. canSetToUpperLevel
  208. ^ self element parentNode notNil
  209. !
  210. isInvalid
  211. ^ self element isNil
  212. ! !
  213. !Domite class methodsFor: 'converting'!
  214. domListAsArray: aDomList
  215. <return Array.prototype.slice.call(aDomList)>
  216. !
  217. wrap: aCollection
  218. ^ aCollection collect: [ :each | self fromElement: each ]
  219. ! !
  220. !Domite class methodsFor: 'events'!
  221. createCustomEvent: aString detail: anObject
  222. <return new CustomEvent(aString, {detail: anObject})>
  223. ! !
  224. !Domite class methodsFor: 'instance creation'!
  225. allAt: aString
  226. ^ self wrap: (self domListAsArray: (document querySelectorAll: aString))
  227. !
  228. at: aString
  229. ^ self fromElement: (document querySelector: aString)
  230. !
  231. fromElement: aDomElement
  232. aDomElement ifNotNil: [
  233. (self isDomNode: aDomElement) ifFalse: [
  234. self error: self name, ': Need a DOM node' ]].
  235. ^ self new
  236. element: aDomElement;
  237. yourself
  238. !
  239. fromElement: aDomElement cursorBefore: anotherDomElement
  240. ^ (self fromElement: aDomElement)
  241. reference: anotherDomElement;
  242. yourself
  243. !
  244. newElement: aString
  245. ^ self fromElement: (document createElement: aString)
  246. !
  247. newElement: aString xmlns: anotherString
  248. ^ self fromElement: (document createElementNS: anotherString tagName: aString)
  249. !
  250. newStream
  251. ^ self fromElement: document createDocumentFragment
  252. ! !
  253. !Domite class methodsFor: 'testing'!
  254. isDomNode: anObject
  255. <
  256. return anObject.nodeType >> 0 &&
  257. Object.prototype.toString.call(anObject) !!== "[object Object]"
  258. >
  259. ! !
  260. !ProtoStream methodsFor: '*DOMite'!
  261. nextPutDomNode: aNode
  262. self nextPut: aNode
  263. ! !