Last active
January 6, 2017 18:01
-
-
Save bloodyowl/cd81137d112dc3f9bfca to your computer and use it in GitHub Desktop.
POC: minimal Backbone subset written in ES6/ES7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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