DOMite.st 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. Smalltalk createPackage: 'DOMite'!
  2. (Smalltalk packageAt: 'DOMite' ifAbsent: [ self error: 'Package not created: DOMite' ]) imports: {'amber/core/Platform-DOM'}!
  3. ProtoStream subclass: #Domite
  4. slots: {#element. #reference}
  5. package: 'DOMite'!
  6. !Domite commentStamp!
  7. I am (hopefully thin) wrapper around the notion of "cursor in a page".
  8. I represent a DOM node _and_ a point where
  9. to insert new content into it.
  10. So I play both the role of a container that inserts
  11. as well as the role of an element being inserted.
  12. I inherit from `ProtoStream`.
  13. Creation API:
  14. - `Domite new` creates an insertion point at the bottom of `<body>`.
  15. - `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.
  16. - `Domite fromElement: aDomElement` wraps an element and set the cursor to its end.
  17. - `Domite newElement: 'div'` creates new `<div />` element (and returns it wrapped as a Domite).
  18. CSS selector API:
  19. - `Domite at: aSelector` wraps an element found by `document.querySelector(aSelector)`.
  20. - `aDomite at: aSelector` wraps an element found by `element.querySelector(aSelector)`.
  21. - `Domite allAt: aSelector` return collection of wrapped results of `document.querySelectorAll(aSelector)`.
  22. - `aDomite allAt: aSelector` return collection of wrapped results of `element.querySelectorAll(aSelector)`.
  23. Manipulation API:
  24. - `aDomite << obj` inserts obj at the insertion point.
  25. - `aDomite resetContents` deletes contents of the wrapped element.
  26. - `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).
  27. - `aDomite attrAt: aString` returns attribute of the wrapped element or nil.
  28. - `aDomite attrAt: aString put: anotherString` sets an attribute of the wrapped element.
  29. - `aDomite propAt: aString` returns JS property of the wrapped element or nil.
  30. - `aDomite propAt: aString put: anObject` sets JS property of the wrapped element.
  31. Cursor moving API:
  32. Take this sample HTML, where `[n]` are just markers, not real content:
  33. ```
  34. <body>
  35. <h1>header</h1>
  36. [4]<p>[2]Hello[1]world[3]</p>[5]
  37. <small>footer</small>
  38. </body>
  39. ```
  40. If `d` is a `Domite` representing `[1]`, then:
  41. - `d setToStart` would move `d` to be at `[2]`,
  42. - `d setToEnd` would move `d` to be at `[3]`,
  43. - `d setToBefore` would move `d` to be at `[4]`, and
  44. - `d setToAfter` would move `d` to be at `[5]`.
  45. It is not presumed one would use `setToXxx`
  46. to actually move around in a single instance.
  47. It is envisioned this API will be used mostly
  48. in combination with `copy`, like
  49. `afterMe := self copy setToAfter`.
  50. Event API:
  51. - `aDomite on: aString bind: [ :ev | ... ]` binds a block to process an event.
  52. - `aDomite off: aString unbind: aBlock` unbinds the block from processing an event.
  53. - `aDomite fire: aString [detail: anObject]` triggers a CustomEvent with specified type and, optionally, a detail object.
  54. - `aDomite fireEvent: anEvent` triggers existing DOM Event object!
  55. !Domite methodsFor: 'accessing'!
  56. allAt: aString
  57. ^ self class wrap: (PlatformDom toArray: (self element querySelectorAll: aString))
  58. !
  59. at: aString
  60. ^ self class fromElement: (self element querySelector: aString)
  61. !
  62. attrAt: aString
  63. (element hasAttribute: aString)
  64. ifTrue: [ ^ element getAttribute: aString ]
  65. ifFalse: [ ^ nil ]
  66. !
  67. attrAt: aString put: anotherString
  68. element setAttribute: aString to: anotherString
  69. !
  70. element
  71. ^ element
  72. !
  73. element: anObject
  74. element := anObject
  75. !
  76. propAt: aString
  77. ^ element at: aString
  78. !
  79. propAt: aString put: anObject
  80. ^ element at: aString put: anObject
  81. !
  82. reference
  83. ^ reference
  84. !
  85. reference: anObject
  86. reference := anObject
  87. ! !
  88. !Domite methodsFor: 'comparing'!
  89. = aDomite
  90. ^ self class = aDomite class and: [
  91. self element = aDomite element and: [
  92. self reference = aDomite reference ]]
  93. ! !
  94. !Domite methodsFor: 'converting'!
  95. asDomNode
  96. ^ element
  97. !
  98. asJQuery
  99. ^ self asDomNode asJQuery
  100. ! !
  101. !Domite methodsFor: 'deletion'!
  102. cutUpTo: aDomite
  103. <inlineJS: '
  104. var result = document.createDocumentFragment(),
  105. start = $self.reference,
  106. end = aDomite.reference,
  107. tmp;
  108. while (start && start !!= end) {
  109. tmp = start;
  110. start = start.nextSibling;
  111. result.appendChild(tmp);
  112. }
  113. $self.reference = start;
  114. return $self._class()._fromElement_(result);
  115. '>
  116. !
  117. resetContents
  118. <inlineJS: '
  119. var element = $self.element, child;
  120. while (child = element.firstChild) element.removeChild(child);
  121. $self.reference = null;
  122. '>
  123. ! !
  124. !Domite methodsFor: 'events'!
  125. fire: aString
  126. self fire: aString detail: nil asJavaScriptObject
  127. !
  128. fire: aString detail: anObject
  129. self fireEvent: (
  130. PlatformDom newCustomEvent: aString detail: anObject)
  131. !
  132. fireEvent: anEvent
  133. self asDomNode dispatchEvent: anEvent
  134. !
  135. off: aString unbind: aBlock
  136. self asDomNode removeEventListener: aString block: aBlock useCapture: false
  137. !
  138. on: aString bind: aBlock
  139. self asDomNode addEventListener: aString block: aBlock useCapture: false
  140. ! !
  141. !Domite methodsFor: 'initialization'!
  142. initialize
  143. super initialize.
  144. element := document body.
  145. reference := nil asJavaScriptObject
  146. ! !
  147. !Domite methodsFor: 'insertion'!
  148. nextPut: anObject
  149. self nextPutString: anObject printString
  150. !
  151. nextPutDomNode: aDomElement
  152. self element
  153. insertBefore: aDomElement
  154. reference: self reference
  155. !
  156. nextPutJSObject: aJSObject
  157. (PlatformDom isDomNode: aJSObject)
  158. ifTrue: [ self nextPutDomNode: aJSObject ]
  159. ifFalse: [ self nextPut: aJSObject ]
  160. !
  161. nextPutString: aString
  162. self nextPutDomNode: aString asString asDomNode
  163. ! !
  164. !Domite methodsFor: 'positioning'!
  165. reset
  166. self reference: self element firstChild
  167. !
  168. setToAfter
  169. self
  170. reference: self element nextSibling;
  171. element: self element parentNode
  172. !
  173. setToBack
  174. self atStart ifFalse: [
  175. self reference: (self reference
  176. ifNil: [ self element lastChild ]
  177. ifNotNil: [ :ref | ref previousSibling ])]
  178. !
  179. setToBefore
  180. self
  181. reference: self element;
  182. element: self element parentNode
  183. !
  184. setToEnd
  185. self reference: nil asJavaScriptObject "null"
  186. !
  187. setToNext
  188. self atEnd ifFalse: [
  189. self reference: self reference nextSibling ]
  190. !
  191. setToPrev
  192. self deprecatedAPI: 'Use #setToBack instead'.
  193. ^ self setToBack
  194. ! !
  195. !Domite methodsFor: 'reading'!
  196. back
  197. self atStart
  198. ifTrue: [ ^ nil ]
  199. ifFalse: [ self setToBack. ^ self peek ]
  200. !
  201. next
  202. self atEnd
  203. ifTrue: [ ^ nil ]
  204. ifFalse: [ | result |
  205. result := self peek.
  206. self setToNext.
  207. ^ result ]
  208. !
  209. peek
  210. ^ self reference
  211. ifNotNil: [ :ref | self class fromElement: ref ]
  212. !
  213. prev
  214. self deprecatedAPI: 'Use #back instead.'.
  215. ^ self back
  216. ! !
  217. !Domite methodsFor: 'streaming'!
  218. putOn: aStream
  219. aStream nextPutDomNode: self asDomNode
  220. ! !
  221. !Domite methodsFor: 'testing'!
  222. atEnd
  223. ^ self reference isNil
  224. !
  225. atStart
  226. ^ self reference = self element firstChild
  227. !
  228. canSetToUpperLevel
  229. ^ self element parentNode notNil
  230. !
  231. isInvalid
  232. ^ self element isNil
  233. ! !
  234. !Domite class methodsFor: 'converting'!
  235. wrap: aCollection
  236. ^ aCollection collect: [ :each | self fromElement: each ]
  237. ! !
  238. !Domite class methodsFor: 'instance creation'!
  239. allAt: aString
  240. ^ self wrap: (PlatformDom toArray: (document querySelectorAll: aString))
  241. !
  242. at: aString
  243. ^ self fromElement: (document querySelector: aString)
  244. !
  245. fromElement: aDomElement
  246. aDomElement ifNotNil: [
  247. (PlatformDom isDomNode: aDomElement) ifFalse: [
  248. self error: self name, ': Need a DOM node' ]].
  249. ^ self new
  250. element: aDomElement;
  251. yourself
  252. !
  253. fromElement: aDomElement cursorBefore: anotherDomElement
  254. ^ (self fromElement: aDomElement)
  255. reference: anotherDomElement;
  256. yourself
  257. !
  258. newElement: aString
  259. ^ self fromElement: (document createElement: aString)
  260. !
  261. newElement: aString xmlns: anotherString
  262. ^ self fromElement: (document createElementNS: anotherString tagName: aString)
  263. !
  264. newStream
  265. ^ self fromElement: #() asDomNode
  266. ! !
  267. !ProtoStream methodsFor: '*DOMite'!
  268. nextPutDomNode: aNode
  269. self nextPut: aNode
  270. ! !