123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- Smalltalk createPackage: 'DOMite'!
- (Smalltalk packageAt: 'DOMite' ifAbsent: [ self error: 'Package not created: DOMite' ]) imports: {'amber_core/Platform-DOM'}!
- ProtoStream subclass: #Domite
- instanceVariableNames: 'element reference'
- package: 'DOMite'!
- !Domite commentStamp!
- I am (hopefully thin) wrapper around the notion of "cursor in a page".
- I represent a DOM node _and_ a point where
- to insert new content into it.
- So I play both the role of a container that inserts
- as well as the role of an element being inserted.
- I inherit from `ProtoStream`.
- Creation API:
- - `Domite new` creates an insertion point at the bottom of `<body>`.
- - `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.
- - `Domite fromElement: aDomElement` wraps an element and set the cursor to its end.
- - `Domite newElement: 'div'` creates new `<div />` element (and returns it wrapped as a Domite).
- CSS selector API:
- - `Domite at: aSelector` wraps an element found by `document.querySelector(aSelector)`.
- - `aDomite at: aSelector` wraps an element found by `element.querySelector(aSelector)`.
- - `Domite allAt: aSelector` return collection of wrapped results of `document.querySelectorAll(aSelector)`.
- - `aDomite allAt: aSelector` return collection of wrapped results of `element.querySelectorAll(aSelector)`.
- Manipulation API:
- - `aDomite << obj` inserts obj at the insertion point.
- - `aDomite resetContents` deletes contents of the wrapped element.
- - `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).
- - `aDomite attrAt: aString` returns attribute of the wrapped element or nil.
- - `aDomite attrAt: aString put: anotherString` sets an attribute of the wrapped element.
- - `aDomite propAt: aString` returns JS property of the wrapped element or nil.
- - `aDomite propAt: aString put: anObject` sets JS property of the wrapped element.
- Cursor moving API:
- Take this sample HTML, where `[n]` are just markers, not real content:
- ```
- <body>
- <h1>header</h1>
- [4]<p>[2]Hello[1]world[3]</p>[5]
- <small>footer</small>
- </body>
- ```
- If `d` is a `Domite` representing `[1]`, then:
- - `d setToStart` would move `d` to be at `[2]`,
- - `d setToEnd` would move `d` to be at `[3]`,
- - `d setToBefore` would move `d` to be at `[4]`, and
- - `d setToAfter` would move `d` to be at `[5]`.
- It is not presumed one would use `setToXxx`
- to actually move around in a single instance.
- It is envisioned this API will be used mostly
- in combination with `copy`, like
- `afterMe := self copy setToAfter`.
- Event API:
- - `aDomite on: aString bind: [ :ev | ... ]` binds a block to process an event.
- - `aDomite off: aString unbind: aBlock` unbinds the block from processing an event.
- - `aDomite fire: aString [detail: anObject]` triggers a CustomEvent with specified type and, optionally, a detail object.
- - `aDomite fireEvent: anEvent` triggers existing DOM Event object!
- !Domite methodsFor: 'accessing'!
- allAt: aString
- ^ self class wrap: (PlatformDom toArray: (self element querySelectorAll: aString))
- !
- at: aString
- ^ self class fromElement: (self element querySelector: aString)
- !
- attrAt: aString
- (element hasAttribute: aString)
- ifTrue: [ ^ element getAttribute: aString ]
- ifFalse: [ ^ nil ]
- !
- attrAt: aString put: anotherString
- element setAttribute: aString to: anotherString
- !
- element
- ^ element
- !
- element: anObject
- element := anObject
- !
- propAt: aString
- ^ element at: aString
- !
- propAt: aString put: anObject
- ^ element at: aString put: anObject
- !
- reference
- ^ reference
- !
- reference: anObject
- reference := anObject
- ! !
- !Domite methodsFor: 'comparing'!
- = aDomite
- ^ self class = aDomite class and: [
- self element = aDomite element and: [
- self reference = aDomite reference ]]
- ! !
- !Domite methodsFor: 'converting'!
- asDomNode
- ^ element
- !
- asJQuery
- ^ self asDomNode asJQuery
- ! !
- !Domite methodsFor: 'deletion'!
- cutUpTo: aDomite
- <inlineJS: '
- var result = document.createDocumentFragment(),
- start = $self["@reference"],
- end = aDomite["@reference"],
- tmp;
- while (start && start !!= end) {
- tmp = start;
- start = start.nextSibling;
- result.appendChild(tmp);
- }
- $self["@reference"] = start;
- return $self._class()._fromElement_(result);
- '>
- !
- resetContents
- <inlineJS: '
- var element = $self["@element"], child;
- while (child = element.firstChild) element.removeChild(child);
- $self["@reference"] = null;
- '>
- ! !
- !Domite methodsFor: 'events'!
- fire: aString
- self fire: aString detail: nil asJavaScriptObject
- !
- fire: aString detail: anObject
- self fireEvent: (
- PlatformDom newCustomEvent: aString detail: anObject)
- !
- fireEvent: anEvent
- self asDomNode dispatchEvent: anEvent
- !
- off: aString unbind: aBlock
- self asDomNode removeEventListener: aString block: aBlock useCapture: false
- !
- on: aString bind: aBlock
- self asDomNode addEventListener: aString block: aBlock useCapture: false
- ! !
- !Domite methodsFor: 'initialization'!
- initialize
- super initialize.
- element := document body.
- reference := nil asJavaScriptObject
- ! !
- !Domite methodsFor: 'insertion'!
- nextPut: anObject
- self nextPutString: anObject printString
- !
- nextPutDomNode: aDomElement
- self element
- insertBefore: aDomElement
- reference: self reference
- !
- nextPutJSObject: aJSObject
- (PlatformDom isDomNode: aJSObject)
- ifTrue: [ self nextPutDomNode: aJSObject ]
- ifFalse: [ self nextPut: aJSObject ]
- !
- nextPutString: aString
- self nextPutDomNode: aString asString asDomNode
- ! !
- !Domite methodsFor: 'positioning'!
- reset
- self reference: self element firstChild
- !
- setToAfter
- self
- reference: self element nextSibling;
- element: self element parentNode
- !
- setToBack
- self atStart ifFalse: [
- self reference: (self reference
- ifNil: [ self element lastChild ]
- ifNotNil: [ :ref | ref previousSibling ])]
- !
- setToBefore
- self
- reference: self element;
- element: self element parentNode
- !
- setToEnd
- self reference: nil asJavaScriptObject "null"
- !
- setToNext
- self atEnd ifFalse: [
- self reference: self reference nextSibling ]
- !
- setToPrev
- self deprecatedAPI: 'Use #setToBack instead'.
- ^ self setToBack
- ! !
- !Domite methodsFor: 'reading'!
- back
- self atStart
- ifTrue: [ ^ nil ]
- ifFalse: [ self setToBack. ^ self peek ]
- !
- next
- self atEnd
- ifTrue: [ ^ nil ]
- ifFalse: [ | result |
- result := self peek.
- self setToNext.
- ^ result ]
- !
- peek
- ^ self reference
- ifNotNil: [ :ref | self class fromElement: ref ]
- !
- prev
- self deprecatedAPI: 'Use #back instead.'.
- ^ self back
- ! !
- !Domite methodsFor: 'streaming'!
- putOn: aStream
- aStream nextPutDomNode: self asDomNode
- ! !
- !Domite methodsFor: 'testing'!
- atEnd
- ^ self reference isNil
- !
- atStart
- ^ self reference = self element firstChild
- !
- canSetToUpperLevel
- ^ self element parentNode notNil
- !
- isInvalid
- ^ self element isNil
- ! !
- !Domite class methodsFor: 'converting'!
- wrap: aCollection
- ^ aCollection collect: [ :each | self fromElement: each ]
- ! !
- !Domite class methodsFor: 'instance creation'!
- allAt: aString
- ^ self wrap: (PlatformDom toArray: (document querySelectorAll: aString))
- !
- at: aString
- ^ self fromElement: (document querySelector: aString)
- !
- fromElement: aDomElement
- aDomElement ifNotNil: [
- (PlatformDom isDomNode: aDomElement) ifFalse: [
- self error: self name, ': Need a DOM node' ]].
- ^ self new
- element: aDomElement;
- yourself
- !
- fromElement: aDomElement cursorBefore: anotherDomElement
- ^ (self fromElement: aDomElement)
- reference: anotherDomElement;
- yourself
- !
- newElement: aString
- ^ self fromElement: (document createElement: aString)
- !
- newElement: aString xmlns: anotherString
- ^ self fromElement: (document createElementNS: anotherString tagName: aString)
- !
- newStream
- ^ self fromElement: #() asDomNode
- ! !
- !ProtoStream methodsFor: '*DOMite'!
- nextPutDomNode: aNode
- self nextPut: aNode
- ! !
|