Relocate all the things

This commit is contained in:
Maxime Thirouin
2014-10-14 08:23:56 +02:00
parent 414650113b
commit 165532d43c
33 changed files with 12512 additions and 16 deletions

12
lib/clone.js Normal file
View File

@@ -0,0 +1,12 @@
module.exports = function(obj) {
if (null === obj || "object" != typeof obj) {
return obj
}
var copy = obj.constructor()
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) {
copy[attr] = obj[attr]
}
}
return copy
}

29
lib/eval-script.js Normal file
View File

@@ -0,0 +1,29 @@
module.exports = function(el) {
// console.log("going to execute script", el)
var code = (el.text || el.textContent || el.innerHTML || "")
var head = document.querySelector("head") || document.documentElement
var script = document.createElement("script")
if (code.match("document.write")) {
if (console && console.log) {
console.log("Script contains document.write. Cant be executed correctly. Code skipped ", el)
}
return false
}
script.type = "text/javascript"
try {
script.appendChild(document.createTextNode(code))
}
catch (e) {
// old IEs have funky script nodes
script.text = code
}
// execute
head.insertBefore(script, head.firstChild)
head.removeChild(script) // avoid pollution
return true
}

11
lib/events/off.js Normal file
View File

@@ -0,0 +1,11 @@
var forEachEls = require("../foreach-els")
module.exports = function(els, events, listener, useCapture) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
forEachEls(els, function(el) {
el.removeEventListener(e, listener, useCapture)
})
})
}

11
lib/events/on.js Normal file
View File

@@ -0,0 +1,11 @@
var forEachEls = require("../foreach-els")
module.exports = function(els, events, listener, useCapture) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
forEachEls(els, function(el) {
el.addEventListener(e, listener, useCapture)
})
})
}

31
lib/events/trigger.js Normal file
View File

@@ -0,0 +1,31 @@
var forEachEls = require("../foreach-els")
module.exports = function(els, events, opts) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
var event // = new CustomEvent(e) // doesn't everywhere yet
event = document.createEvent("HTMLEvents")
event.initEvent(e, true, true)
event.eventName = e
if (opts) {
Object.keys(opts).forEach(function(key) {
event[key] = opts[key]
})
}
forEachEls(els, function(el) {
var domFix = false
if (!el.parentNode) {
// THANKS YOU IE (9/10//11 concerned)
// dispatchEvent doesn't work if element is not in the dom
domFix = true
document.body.appendChild(el)
}
el.dispatchEvent(event)
if (domFix) {
el.parentNode.removeChild(el)
}
})
})
}

15
lib/execute-scripts.js Normal file
View File

@@ -0,0 +1,15 @@
var forEachEls = require("./foreach-els")
var evalScript = require("./eval-script")
// Finds and executes scripts (used for newly added elements)
// Needed since innerHTML does not run scripts
module.exports = function(el) {
// console.log("going to execute scripts for ", el)
forEachEls(el.querySelectorAll("script"), function(script) {
if (!script.type || script.type.toLowerCase() === "text/javascript") {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
evalScript(script)
}
})
}

7
lib/foreach-els.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = function(els, fn, context) {
if (els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array) {
return Array.prototype.forEach.call(els, fn, context)
}
// assume simple dom element
return fn.call(context, els)
}

8
lib/foreach-selectors.js Normal file
View File

@@ -0,0 +1,8 @@
var forEachEls = require("./foreach-els")
module.exports = function(selectors, cb, context, DOMcontext) {
DOMcontext = DOMcontext || document
selectors.forEach(function(selector) {
forEachEls(DOMcontext.querySelectorAll(selector), cb, context)
})
}

8
lib/is-supported.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = function() {
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
return window.history &&
window.history.pushState &&
window.history.replaceState &&
// pushState isnt reliable on iOS until 5.
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
}

View File

@@ -0,0 +1,20 @@
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable")
}
var aArgs = Array.prototype.slice.call(arguments, 1)
var fToBind = this
var fNOP = function () {}
var fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)))
}
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}
}

76
lib/proto/attach-link.js Normal file
View File

@@ -0,0 +1,76 @@
require("../polyfills/Function.prototype.bind")
var on = require("../events/on")
var clone = require("../clone")
var attrClick = "data-pjax-click-state"
var attrKey = "data-pjax-keyup-state"
var linkAction = function(el, event) {
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
el.setAttribute(attrClick, "modifier")
return
}
// we do test on href now to prevent unexpected behavior if for some reason
// user have href that can be dynamically updated
// Ignore external links.
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
el.setAttribute(attrClick, "external")
return
}
// Ignore click if we are on an anchor on the same page
if (el.pathname === window.location.pathname && el.hash.length > 0) {
el.setAttribute(attrClick, "anchor-present")
return
}
// Ignore anchors on the same page (keep native behavior)
if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) {
el.setAttribute(attrClick, "anchor")
return
}
// Ignore empty anchor "foo.html#"
if (el.href === window.location.href.split("#")[0] + "#") {
el.setAttribute(attrClick, "anchor-empty")
return
}
event.preventDefault()
// dont do "nothing" if user try to reload the page by clicking the same link twice
if (el.href === window.location.href.split("#")[0]) {
el.setAttribute(attrClick, "refresh")
this.refresh()
return
}
el.setAttribute(attrClick, "load")
this.loadUrl(el.href, clone(this.options))
}
module.exports = function(el) {
var instance = this
on(el, "click", function(event) {
linkAction.call(instance, el, event)
})
on(el, "keyup", function(event) {
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
el.setAttribute(attrKey, "modifier")
return
}
if(event.keyCode == 13) {
linkAction.call(instance, el, event)
}
}.bind(this))
}

View File

@@ -0,0 +1,3 @@
module.exports = function(el) {
return el.querySelectorAll(this.options.elements)
}

11
lib/proto/log.js Normal file
View File

@@ -0,0 +1,11 @@
module.exports = function() {
if (this.options.debug && console) {
if (typeof console.log === "function") {
console.log.apply(console, arguments);
}
// ie is weird
else if (console.log) {
console.log(arguments);
}
}
}

7
lib/proto/parse-dom.js Normal file
View File

@@ -0,0 +1,7 @@
var forEachEls = require("../foreach-els")
var parseElement = require("../parse-element")
module.exports = function(el) {
forEachEls(this.getElements(el), parseElement }, this)
}

View File

@@ -0,0 +1,14 @@
module.exports = function(el) {
switch (el.tagName.toLowerCase()) {
case "a":
this.attachLink(el)
break
case "form":
throw "Pjax doesnt support <form> yet."
break
default:
throw "Pjax can only be applied on <a> or <form> submit"
}
}

3
lib/refresh.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = function() {
window.location.reload()
}

19
lib/request.js Normal file
View File

@@ -0,0 +1,19 @@
module.exports = function(location, callback) {
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
callback(request.responseText, request)
}
else {
callback(null, request)
}
}
}
request.open("GET", location + (!/[?&]/.test(location) ? "?": "&") + (new Date().getTime()), true)
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
request.send(null)
return request
}

35
lib/switches-selectors.js Normal file
View File

@@ -0,0 +1,35 @@
var forEachEls = require("./foreach-els")
var defaultSwitches = require("./switches")
module.exports = function(switches, switchesOptions, selectors, fromEl, toEl, options) {
selectors.forEach(function(selector) {
var newEls = fromEl.querySelectorAll(selector)
var oldEls = toEl.querySelectorAll(selector)
if (this.log) {
this.log("Pjax switch", selector, newEls, oldEls)
}
if (newEls.length !== oldEls.length) {
// forEachEls(newEls, function(el) {
// this.log("newEl", el, el.outerHTML)
// }, this)
// forEachEls(oldEls, function(el) {
// this.log("oldEl", el, el.outerHTML)
// }, this)
throw "DOM doesnt look the same on new loaded page: " + selector + " - new " + newEls.length + ", old " + oldEls.length
}
forEachEls(newEls, function(newEl, i) {
var oldEl = oldEls[i]
if (this.log) {
this.log("newEl", newEl, "oldEl", oldEl)
}
if (switches[selector]) {
switches[selector].bind(this)(oldEl, newEl, options, switchesOptions[selector])
}
else {
defaultSwitches.outerHTML.bind(this)(oldEl, newEl, options)
}
}, this)
}, this)
}

110
lib/switches.js Normal file
View File

@@ -0,0 +1,110 @@
module.exports = {
outerHTML: function(oldEl, newEl, options) {
oldEl.outerHTML = newEl.outerHTML
this.onSwitch()
},
innerHTML: function(oldEl, newEl, options) {
oldEl.innerHTML = newEl.innerHTML
oldEl.className = newEl.className
this.onSwitch()
},
sideBySide: function(oldEl, newEl, options, switchOptions) {
var forEach = Array.prototype.forEach
var elsToRemove = []
var elsToAdd = []
var fragToAppend = document.createDocumentFragment()
// height transition are shitty on safari
// so commented for now (until I found something ?)
//var relevantHeight = 0
var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"
var animatedElsNumber = 0
var sexyAnimationEnd = function(e) {
if (e.target != e.currentTarget) {
// end triggered by an animation on a child
return
}
animatedElsNumber--
if (animatedElsNumber <= 0 && elsToRemove) {
elsToRemove.forEach(function(el) {
// browsing quickly can make the el
// already removed by last page update ?
if (el.parentNode) {
el.parentNode.removeChild(el)
}
})
elsToAdd.forEach(function(el) {
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
el.removeAttribute("data-pjax-classes")
// Pjax.off(el, animationEventNames, sexyAnimationEnd, true)
})
elsToAdd = null // free memory
elsToRemove = null // free memory
// assume the height is now useless (avoid bug since there is overflow hidden on the parent)
// oldEl.style.height = "auto"
// this is to trigger some repaint (example: picturefill)
this.onSwitch()
//Pjax.trigger(window, "scroll")
}
}.bind(this)
// Force height to be able to trigger css animation
// here we get the relevant height
// oldEl.parentNode.appendChild(newEl)
// relevantHeight = newEl.getBoundingClientRect().height
// oldEl.parentNode.removeChild(newEl)
// oldEl.style.height = oldEl.getBoundingClientRect().height + "px"
switchOptions = switchOptions || {}
forEach.call(oldEl.childNodes, function(el) {
elsToRemove.push(el)
if (el.classList && !el.classList.contains("js-Pjax-remove")) {
// for fast switch, clean element that just have been added, & not cleaned yet.
if (el.hasAttribute("data-pjax-classes")) {
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
el.removeAttribute("data-pjax-classes")
}
el.classList.add("js-Pjax-remove")
if (switchOptions.callbacks && switchOptions.callbacks.removeElement) {
switchOptions.callbacks.removeElement(el)
}
if (switchOptions.classNames) {
el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward)
}
animatedElsNumber++
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
}
})
forEach.call(newEl.childNodes, function(el) {
if (el.classList) {
var addClasses = ""
if (switchOptions.classNames) {
addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward)
}
if (switchOptions.callbacks && switchOptions.callbacks.addElement) {
switchOptions.callbacks.addElement(el)
}
el.className += addClasses
el.setAttribute("data-pjax-classes", addClasses)
elsToAdd.push(el)
fragToAppend.appendChild(el)
animatedElsNumber++
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
}
})
// pass all className of the parent
oldEl.className = newEl.className
oldEl.appendChild(fragToAppend)
// oldEl.style.height = relevantHeight + "px"
}
}

1
lib/uniqueid.js Normal file
View File

@@ -0,0 +1 @@
module.exports = "pjax" + (new Date().getTime())