Skip to content

Instantly share code, notes, and snippets.

@bloodyowl
Last active January 6, 2017 18:01
Show Gist options
  • Save bloodyowl/cd81137d112dc3f9bfca to your computer and use it in GitHub Desktop.
Save bloodyowl/cd81137d112dc3f9bfca to your computer and use it in GitHub Desktop.
POC: minimal Backbone subset written in ES6/ES7

POC: minimal Backbone subset written in ES6/ES7

import DOMMatchesSelector from "./DOMMatchesSelector"
export default function createDOMEventListener({
rootElement = null,
selector = null,
listener,
capture = false,
thisValue,
}) {
if(typeof selector === "string") {
return function(event) {
const match = DOMMatchesSelector(rootElement, event.target, selector)
if(!match) {
return
}
listener.call(thisValue, event, match)
}
}
return function(event) {
listener.call(thisValue, event)
}
}
const documentElement = document.documentElement
var nativeMatchesSelector =
documentElement.matches ||
documentElement.matchesSelector ||
documentElement.webkitMatchesSelector ||
documentElement.mozMatchesSelector ||
documentElement.oMatchesSelector ||
documentElement.msMatchesSelector ||
function(selector) {
const element = this
const parent = element.parentNode
if(!parent) {
return false
}
let index = -1
const match = parent.querySelectorAll(selector)
const length = match.length
while(++index < length) {
if(match[index] === element) {
return true
}
}
return false
}
if(!nativeMatchesSelector) {
throw new Error("this browser does not support .matchesSelector")
}
export default function(rootNode, node, selector){
if(node === rootNode) {
return null
}
if(nativeMatchesSelector.call(node, selector)) {
return node
}
while(node = node.parentNode) {
if(node === rootNode) {
break
}
if(node.nodeType !== 1) {
break
}
if(nativeMatchesSelector.call(node, selector)) {
return node
}
}
return null
}
import invariant from "../utils/invariant"
const EventTarget = {
addEventListener(target, type, listener, capture = false) {
if(target.addEventListener) {
target.addEventListener(
type,
listener,
capture
)
return
}
invariant(
capture === false,
"EventTarget: your browser doesn't support event capturing"
)
target.attachEvent(
`on${ type }`,
listener
)
},
removeEventListener(target, type, listener, capture = false) {
if(target.removeEventListener) {
target.removeEventListener(
type,
listener,
capture
)
return
}
invariant(
capture === false,
"EventTarget: your browser doesn't support event capturing"
)
target.attachEvent(
`on${ type }`,
listener
)
}
}
export default EventTarget
import EventEmitter from "events"
import Model from "./Model"
import View from "./View"
const Backbone = {
Model: Model,
View: View,
EventEmitter: EventEmitter,
version: __VERSION__,
}
export default Backbone
import EventEmitter from "events"
import shallowCompare from "./shallowCompare"
const attributeMap = new WeakMap()
class Model extends EventEmitter {
errors = null
constructor(initialAttributes = {}) {
super()
attributeMap.set(this, initialAttributes)
}
set(nextAttributes) {
const previousAttributes = attributeMap.get(this)
attributeMap.set(this, {
...previousAttributes,
...nextAttributes,
})
const comparison = shallowCompare(previousAttributes, nextAttributes)
comparison.forEach((change) => {
this.emit(change.type, change)
})
}
get(property) {
return attributeMap.get(this)[property]
}
has(property) {
return attributeMap.get(this).hasOwnProperty(property)
}
validate(object) {
return null
}
isValid() {
const errors = this.validate(attributeMap.get(this))
if(errors) {
this.errors = errors
} else {
this.errors = null
}
return Boolean(errors)
}
toJSON() {
return {
...attributeMap.get(this)
}
}
}
export default Model
export default function shallowCompare(base, nextProperties) {
const nextPropertiesKeys = Object.keys(nextProperties)
const changes = []
let additionsCount = 0
let changesCount = 0
nextPropertiesKeys.forEach((key) => {
const previousValue = base[key]
const value = nextProperties[key]
if(!base.hasOwnProperty(key)) {
changes.push({
type: `addition:${key}`,
previousValue: undefined,
value,
})
++additionsCount
++changesCount
}
if(previousValue !== value) {
changes.push({
type: `change:${key}`,
previousValue,
value,
})
++changesCount
}
})
if(additionsCount) {
changes.push({
type: "addition",
})
}
if(changesCount) {
changes.push({
type: "change",
})
}
return changes
}
export default function invariant(condition, format, ...args) {
if(condition) {
return
}
if(__DEV__) {
let index = -1
throw new TypeError(
`Invariant Error: ${ format.replace(/%s/g, () => args[++index]) }`
)
}
throw new TypeError(
`Invariant Error: please check development version`
)
}
import EventEmitter from "events"
import EventTarget from "./DOM/EventTarget"
import createDOMEventListener from "./DOM/createDOMEventListener"
const eventMap = new WeakMap()
class View extends EventEmitter {
tagName = "div"
constructor({ element = null }) {
super()
this.element = element || document.createElement(tagName)
this.events = this.getEventListeners()
this.addEventListeners()
}
querySelector(selector) {
return this.element.querySelector(selector)
}
querySelectorAll(selector) {
return this.element.querySelectorAll(selector)
}
remove() {
this.removeEventListeners()
const {element} = this
if(element.parentNode) {
element.parentNode.removeChild(element)
}
}
getEventListeners() {
return null
}
addEventListeners() {
if(!Array.isArray(this.events)) {
return
}
const events = this.events.map((item) => {
return {
...item,
boundListener: createDOMEventListener({
root: this.element,
...item,
}),
}
})
eventMap.set(this, events)
events.forEach((item) => {
EventTarget.addEventListener(
this.element,
item.type,
item.boundListener,
item.capture
)
})
}
removeEventListeners() {
if(!eventMap.has(this)) {
return
}
const events = eventMap.get(this)
events.forEach((item) => {
EventTarget.removeEventListener(
this.element,
item.type,
item.boundListener,
item.capture
)
})
}
render() {
return this
}
}
export default View
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment