DOMite.st 7.6 KB

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