update
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
window.pywebview = {
|
||||
token: '%(token)s',
|
||||
platform: '%(platform)s',
|
||||
api: {},
|
||||
_eventHandlers: {},
|
||||
_returnValuesCallbacks: {},
|
||||
|
||||
_createApi: function (funcList) {
|
||||
function sanitize_params(params) {
|
||||
var reservedWords = [
|
||||
'case',
|
||||
'catch',
|
||||
'const',
|
||||
'debugger',
|
||||
'default',
|
||||
'delete',
|
||||
'do',
|
||||
'export',
|
||||
'extends',
|
||||
'false',
|
||||
'function',
|
||||
'instanceof',
|
||||
'let',
|
||||
'new',
|
||||
'null',
|
||||
'super',
|
||||
'switch',
|
||||
'this',
|
||||
'throw',
|
||||
'true',
|
||||
'typeof',
|
||||
'var',
|
||||
'void',
|
||||
];
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var param = params[i];
|
||||
if (reservedWords.indexOf(param) !== -1) {
|
||||
params[i] = param + '_';
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
for (var i = 0; i < funcList.length; i++) {
|
||||
var element = funcList[i];
|
||||
var funcName = element.func;
|
||||
var params = element.params;
|
||||
|
||||
// Create nested structure and assign function
|
||||
var funcHierarchy = funcName.split('.');
|
||||
var functionName = funcHierarchy.pop();
|
||||
var nestedObject = funcHierarchy.reduce(function (obj, prop) {
|
||||
if (!obj[prop]) {
|
||||
obj[prop] = {};
|
||||
}
|
||||
return obj[prop];
|
||||
}, window.pywebview.api);
|
||||
|
||||
// Define the function body
|
||||
var funcBody =
|
||||
'var __id = (Math.random() + "").substring(2);' +
|
||||
'var promise = new Promise(function(resolve, reject) {' +
|
||||
' window.pywebview._checkValue("' +
|
||||
funcName +
|
||||
'", resolve, reject, __id);' +
|
||||
'});' +
|
||||
'window.pywebview._jsApiCallback("' +
|
||||
funcName +
|
||||
'", Array.prototype.slice.call(arguments), __id);' +
|
||||
'return promise;';
|
||||
|
||||
// Assign the new function
|
||||
nestedObject[functionName] = new Function(
|
||||
sanitize_params(params),
|
||||
funcBody
|
||||
);
|
||||
window.pywebview._returnValuesCallbacks[funcName] = {};
|
||||
}
|
||||
},
|
||||
|
||||
_jsApiCallback: function (funcName, params, id) {
|
||||
switch (window.pywebview.platform) {
|
||||
case 'mshtml':
|
||||
case 'cef':
|
||||
case 'qtwebkit':
|
||||
case 'android-webkit':
|
||||
return window.external.call(funcName, pywebview.stringify(params), id);
|
||||
case 'edgechromium':
|
||||
// Full file path support for WebView2
|
||||
if (
|
||||
params.event instanceof Event &&
|
||||
params.event.type === 'drop' &&
|
||||
params.event.dataTransfer.files
|
||||
) {
|
||||
chrome.webview.postMessageWithAdditionalObjects(
|
||||
'FilesDropped',
|
||||
params.event.dataTransfer.files
|
||||
);
|
||||
}
|
||||
return window.chrome.webview.postMessage([
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id,
|
||||
]);
|
||||
case 'cocoa':
|
||||
case 'gtkwebkit2':
|
||||
return window.webkit.messageHandlers.jsBridge.postMessage(
|
||||
pywebview.stringify(
|
||||
{ funcName: funcName, params: params, id: id }
|
||||
)
|
||||
);
|
||||
case 'qtwebengine':
|
||||
if (!window.pywebview._QWebChannel) {
|
||||
setTimeout(function () {
|
||||
window.pywebview._QWebChannel.objects.external.call(
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id
|
||||
);
|
||||
}, 100);
|
||||
} else {
|
||||
window.pywebview._QWebChannel.objects.external.call(
|
||||
funcName,
|
||||
pywebview.stringify(params),
|
||||
id
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_checkValue: function (funcName, resolve, reject, id) {
|
||||
window.pywebview._returnValuesCallbacks[funcName][id] = function(returnObj) {
|
||||
var value = returnObj.value;
|
||||
var isError = returnObj.isError;
|
||||
|
||||
delete window.pywebview._returnValuesCallbacks[funcName][id];
|
||||
|
||||
if (isError) {
|
||||
var pyError = JSON.parse(value);
|
||||
var error = new Error(pyError.message);
|
||||
error.name = pyError.name;
|
||||
error.stack = pyError.stack;
|
||||
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(value));
|
||||
}
|
||||
};
|
||||
},
|
||||
_asyncCallback: function (result, id) {
|
||||
window.pywebview._jsApiCallback('pywebviewAsyncCallback', result, id);
|
||||
},
|
||||
_isPromise: function (obj) {
|
||||
return (
|
||||
!!obj &&
|
||||
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||
typeof obj.then === 'function'
|
||||
);
|
||||
},
|
||||
|
||||
stringify: function stringify(obj, timing) {
|
||||
function tryConvertToArray(obj) {
|
||||
try {
|
||||
return Array.prototype.slice.call(obj);
|
||||
} catch (e) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function isArrayLike(a) {
|
||||
return (
|
||||
a &&
|
||||
typeof a.length === 'number' &&
|
||||
typeof a !== 'string' &&
|
||||
(Array.isArray(a) ||
|
||||
(typeof a === 'object' &&
|
||||
a.length >= 0 &&
|
||||
(a.length === 0 || (a.length - 1) in a)))
|
||||
);
|
||||
}
|
||||
|
||||
function serialize(obj, ancestors) {
|
||||
try {
|
||||
if (obj instanceof Window) return 'Window';
|
||||
if (obj instanceof Node) {
|
||||
obj = pywebview.domJSON.toJSON(obj, {
|
||||
metadata: false,
|
||||
serialProperties: true,
|
||||
});
|
||||
}
|
||||
|
||||
var boundSerialize = serialize.bind(obj);
|
||||
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
while (
|
||||
ancestors.length > 0 &&
|
||||
ancestors[ancestors.length - 1] !== this
|
||||
) {
|
||||
ancestors.pop();
|
||||
}
|
||||
|
||||
if (ancestors.indexOf(obj) > -1) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
ancestors.push(obj);
|
||||
|
||||
if (isArrayLike(obj)) {
|
||||
obj = tryConvertToArray(obj);
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
var arr = obj.map(function (value) {
|
||||
return boundSerialize(value, ancestors);
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
var newObj = {};
|
||||
for (var key in obj) {
|
||||
if (typeof obj === 'function') {
|
||||
continue;
|
||||
}
|
||||
newObj[key] = boundSerialize(obj[key], ancestors);
|
||||
}
|
||||
return newObj;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
var startTime = +new Date();
|
||||
|
||||
var _serialize = serialize.bind(null);
|
||||
var finalObj = _serialize(obj, []);
|
||||
var result = JSON.stringify(finalObj);
|
||||
|
||||
var endTime = +new Date();
|
||||
if (timing) {
|
||||
console.log('Serialization time: ' + (endTime - startTime) / 1000 + 's');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
_getNodeId: function (element) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
var pywebviewId =
|
||||
element.getAttribute('data-pywebview-id') ||
|
||||
Math.random().toString(36).substr(2, 11);
|
||||
if (!element.hasAttribute('data-pywebview-id')) {
|
||||
element.setAttribute('data-pywebview-id', pywebviewId);
|
||||
}
|
||||
return pywebviewId;
|
||||
},
|
||||
|
||||
_insertNode: function (node, parent, mode) {
|
||||
if (mode === 'LAST_CHILD') {
|
||||
parent.appendChild(node);
|
||||
} else if (mode === 'FIRST_CHILD') {
|
||||
parent.insertBefore(node, parent.firstChild);
|
||||
} else if (mode === 'BEFORE') {
|
||||
parent.parentNode.insertBefore(node, parent);
|
||||
} else if (mode === 'AFTER') {
|
||||
parent.parentNode.insertBefore(node, parent.nextSibling);
|
||||
} else if (mode === 'REPLACE') {
|
||||
parent.parentNode.replaceChild(node, parent);
|
||||
}
|
||||
},
|
||||
|
||||
_loadCss: function (css) {
|
||||
var interval = setInterval(function () {
|
||||
if (document.readyState === 'complete') {
|
||||
clearInterval(interval);
|
||||
|
||||
var cssElement = document.createElement('style');
|
||||
cssElement.type = 'text/css';
|
||||
cssElement.innerHTML = css;
|
||||
document.head.appendChild(cssElement);
|
||||
}
|
||||
}, 10);
|
||||
},
|
||||
|
||||
_processElements: function (elements) {
|
||||
var serializedElements = [];
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var pywebviewId;
|
||||
if (elements[i] === window) {
|
||||
pywebviewId = 'window';
|
||||
} else if (elements[i] === document) {
|
||||
pywebviewId = 'document';
|
||||
} else {
|
||||
pywebviewId = window.pywebview._getNodeId(elements[i]);
|
||||
}
|
||||
|
||||
var node = pywebview.domJSON.toJSON(elements[i], {
|
||||
metadata: false,
|
||||
serialProperties: true,
|
||||
deep: false,
|
||||
});
|
||||
|
||||
node._pywebviewId = pywebviewId;
|
||||
serializedElements.push(node);
|
||||
}
|
||||
|
||||
return serializedElements;
|
||||
},
|
||||
|
||||
_debounce: function (func, delay) {
|
||||
var timeout;
|
||||
return function () {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(function () {
|
||||
func.apply(context, args);
|
||||
}, delay);
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
|
||||
(function() {
|
||||
var platform = window.pywebview.platform;
|
||||
var disableText = '%(text_select)s' === 'False';
|
||||
var disableTextCss = 'body {-webkit-user-select: none; -khtml-user-select: none; -ms-user-select: none; user-select: none; cursor: default;}'
|
||||
|
||||
if (platform == 'mshtml') {
|
||||
window.alert = function(msg) {
|
||||
window.external.alert(msg);
|
||||
}
|
||||
} else if (platform == 'edgechromium') {
|
||||
window.alert = function (message) {
|
||||
window.chrome.webview.postMessage(['_pywebviewAlert', pywebview.stringify(message), 'alert']);
|
||||
}
|
||||
} else if (platform == 'gtkwebkit2') {
|
||||
window.alert = function (message) {
|
||||
window.webkit.messageHandlers.jsBridge.postMessage(pywebview.stringify({funcName: '_pywebviewAlert', params: message, id: 'alert'}));
|
||||
}
|
||||
} else if (platform == 'cocoa') {
|
||||
window.print = function() {
|
||||
window.webkit.messageHandlers.browserDelegate.postMessage('print');
|
||||
}
|
||||
} else if (platform === 'qtwebengine') {
|
||||
window.alert = function (message) {
|
||||
window.pywebview._QWebChannel.objects.external.call('_pywebviewAlert', pywebview.stringify(message), 'alert');
|
||||
}
|
||||
} else if (platform === 'qtwebkit') {
|
||||
window.alert = function (message) {
|
||||
window.external.invoke(JSON.stringify(['_pywebviewAlert', message, 'alert']));
|
||||
}
|
||||
}
|
||||
|
||||
if (disableText) {
|
||||
var css = document.createElement("style");
|
||||
css.type = "text/css";
|
||||
css.innerHTML = disableTextCss;
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
function disableTouchEvents() {
|
||||
var initialX = 0;
|
||||
var initialY = 0;
|
||||
|
||||
function onMouseMove(ev) {
|
||||
var x = ev.screenX - initialX;
|
||||
var y = ev.screenY - initialY;
|
||||
window.pywebview._jsApiCallback('pywebviewMoveWindow', [x, y], 'move');
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
function onMouseDown(ev) {
|
||||
if (
|
||||
'%(drag_region_direct_target_only)s' === 'True' &&
|
||||
!ev.target.matches('%(drag_selector)s')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
initialX = ev.clientX;
|
||||
initialY = ev.clientY;
|
||||
window.addEventListener('mouseup', onMouseUp);
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
}
|
||||
|
||||
function onBodyMouseDown(event) {
|
||||
var target = event.target;
|
||||
var dragSelectorElements = document.querySelectorAll('%(drag_selector)s');
|
||||
|
||||
while (target && target !== document.body && target !== document.documentElement) {
|
||||
if (target.nodeType === 1) {
|
||||
// Check if target matches the drag selector
|
||||
for (var i = 0; i < dragSelectorElements.length; i++) {
|
||||
if (dragSelectorElements[i] === target) {
|
||||
onMouseDown(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it doesn't match, continue up the DOM tree
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('mousedown', onBodyMouseDown);
|
||||
|
||||
// easy drag for edge chromium
|
||||
if ('%(easy_drag)s' === 'True') {
|
||||
window.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
|
||||
if ('%(zoomable)s' === 'False') {
|
||||
document.body.addEventListener('touchstart', function(e) {
|
||||
if ((e.touches.length > 1) || e.targetTouches.length > 1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}, {passive: false});
|
||||
|
||||
window.addEventListener('wheel', function (e) {
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
}
|
||||
|
||||
// draggable
|
||||
if ('%(draggable)s' === 'False') {
|
||||
document.addEventListener('dragstart', function(e) {
|
||||
if (e.target.tagName === 'IMG' || e.target.tagName === 'A') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disableTouchEvents();
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
window.pywebview._createApi(JSON.parse('%(functions)s'));
|
||||
|
||||
if (window.pywebview.platform == 'qtwebengine') {
|
||||
new QWebChannel(qt.webChannelTransport, function(channel) {
|
||||
window.pywebview._QWebChannel = channel;
|
||||
window.dispatchEvent(new CustomEvent('pywebviewready'));
|
||||
});
|
||||
} else {
|
||||
window.dispatchEvent(new CustomEvent('pywebviewready'));
|
||||
}
|
||||
@@ -0,0 +1,823 @@
|
||||
/**
|
||||
* domJSON.js: A simple framework for converting DOM nodes to special JSON objects, and vice versa
|
||||
*
|
||||
* @fileOverview
|
||||
* @author Alex Zaslavsky
|
||||
* @version 0.1.2
|
||||
* @license The MIT License: Copyright (c) 2013 Alex Zaslavsky
|
||||
*/
|
||||
|
||||
|
||||
|
||||
//Load the library
|
||||
;(function(root, factory) {
|
||||
/* istanbul ignore next */
|
||||
if (typeof define === 'function' && define.amd) { //AMD
|
||||
define(function(){
|
||||
return factory(root);
|
||||
});
|
||||
} else if (typeof exports !== 'undefined') { //CommonJS/node.js
|
||||
var domJSON = factory(root);
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = domJSON;
|
||||
}
|
||||
exports = domJSON;
|
||||
} else { //Browser global
|
||||
window.pywebview.domJSON = factory(root);
|
||||
}
|
||||
})(this, function(win){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* domJSON is a global variable to store two methods: `.toJSON()` to convert a DOM Node into a JSON object, and `.toDOM()` to turn that JSON object back into a DOM Node
|
||||
* @namespace domJSON
|
||||
* @global
|
||||
*/
|
||||
var domJSON = {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An object specifying a list of fields and how to filter it, or an array with the first value being an optional boolean to convey the same information
|
||||
* @typedef {Object|Array} FilterList
|
||||
* @property {boolean} [exclude=false] If this is set to `true`, the `filter` property will specify which fields to exclude from the result (boolean difference), not which ones to include (boolean intersection)
|
||||
* @property {string[]} values An array of strings which specify the fields to include/exclude from some broader list
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default metadata for a JSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var metadata = {
|
||||
href: win.location.href || null,
|
||||
userAgent: window.navigator && window.navigator.userAgent ? window.navigator.userAgent : null,
|
||||
version: '0.1.2'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default options for creating the JSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var defaultsForToJSON = {
|
||||
absolutePaths: ['action', 'data', 'href', 'src'],
|
||||
//absStylePaths: ['attr', 'background', 'background-image', 'border-image', 'border-image-source', 'content', 'list-style-image', 'mask-image'], //http://stackoverflow.com/questions/27790925/what-are-all-the-css3-properties-that-accept-urls-or-uris
|
||||
attributes: true,
|
||||
computedStyle: false,
|
||||
cull: true,
|
||||
deep: true,
|
||||
domProperties: true,
|
||||
filter: false,
|
||||
htmlOnly: false,
|
||||
metadata: true,
|
||||
//parse: false,
|
||||
serialProperties: false,
|
||||
stringify: false,
|
||||
allowDangerousElements: false
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default options for creating a DOM node from a previously generated domJSON object
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var defaultsForToDOM = {
|
||||
noMeta: false,
|
||||
allowDangerousElements: false
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of disallowed HTMLElement tags - there is no flexibility here, these cannot be processed by domJSON for security reasons!
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var banned = [
|
||||
'link',
|
||||
'script'
|
||||
]; //Consider (maybe) adding the following tags: iframe, html, audio, video, object
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of node properties that must be copied if they exist; there is no user option that will remove these
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var required = [
|
||||
'nodeType',
|
||||
'nodeValue',
|
||||
'tagName'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of node properties to specifically avoid simply copying; there is no user option that will allow these to be copied directly
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var ignored = [
|
||||
'attributes',
|
||||
'childNodes',
|
||||
'children',
|
||||
'classList',
|
||||
'dataset',
|
||||
'style'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A list of serialized read-only nodes to ignore; these can overwritten if the user specifies the "filter" option
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var serials = [
|
||||
'innerHTML',
|
||||
'innerText',
|
||||
'outerHTML',
|
||||
'outerText',
|
||||
'prefix',
|
||||
'text',
|
||||
'textContent',
|
||||
'wholeText'
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility function to extend an object - useful for synchronizing user-submitted options with default values; same API as underscore extend
|
||||
* @param {Object} [target] The object that will be extended
|
||||
* @param {...Object} [added] Additional objects that will extend the target
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var extend = function(target) {
|
||||
if (!arguments.length) {
|
||||
return arguments[0] || {};
|
||||
}
|
||||
|
||||
//Overwrite matching properties on the target from the added object
|
||||
for (var p in arguments[1]) {
|
||||
target[p] = arguments[1][p];
|
||||
}
|
||||
|
||||
//If we have more arguments, run the function recursively
|
||||
if (arguments.length > 2) {
|
||||
var moreArgs = [target].concat(Array.prototype.slice.call(arguments, 2));
|
||||
return extend.apply( null, moreArgs);
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the unique values (in the order they first appeared) from one or more arrays
|
||||
* @param {...Array} constituent An array to combine into a larger array of unique values
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var unique = function() {
|
||||
if (!arguments.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var all = Array.prototype.concat.apply([], arguments);
|
||||
for (var a = 0; a < all.length; a++) {
|
||||
if (all.indexOf(all[a]) < a) {
|
||||
all.splice(a, 1);
|
||||
a--;
|
||||
}
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make a shallow copy of an object or array
|
||||
* @param {Object|string[]} item The object/array that will be copied
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var copy = function(item) {
|
||||
if (item instanceof Array) {
|
||||
return item.slice();
|
||||
} else {
|
||||
var output = {};
|
||||
for (var i in item) {
|
||||
output[i] = item[i];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Do a boolean intersection between an array/object and a filter array
|
||||
* @param {Object|string[]} item The object/array that will be intersected with the filter
|
||||
* @param {boolean|string[]} filter Specifies which properties to select from the "item" (or element to keep, if "item is an array")
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolInter = function(item, filter) {
|
||||
var output;
|
||||
if (item instanceof Array) {
|
||||
output = unique(item.filter(function(val) { return filter.indexOf(val) > -1; }));
|
||||
} else {
|
||||
output = {};
|
||||
for (var f in filter) {
|
||||
if (item.hasOwnProperty(filter[f])) {
|
||||
output[filter[f]] = item[filter[f]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Do a boolean difference between an array/object and a filter array
|
||||
* @param {Object|string[]} item The object/array that will be differentiated with the filter
|
||||
* @param {boolean|string[]} filter Specifies which properties to exclude from the "item" (or element to remove, if "item is an array")
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolDiff = function(item, filter) {
|
||||
var output;
|
||||
if (item instanceof Array) {
|
||||
output = unique(item.filter(function(val) { return filter.indexOf(val) === -1; }));
|
||||
} else {
|
||||
output = {};
|
||||
for (var i in item) {
|
||||
output[i] = item[i];
|
||||
}
|
||||
for (var f in filter) {
|
||||
if (output.hasOwnProperty(filter[f])) {
|
||||
delete output[filter[f]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether we want to do a boolean intersection or difference
|
||||
* @param {Object|string[]} item The object/array that will be differentiated with the filter
|
||||
* @param {boolean|Array} filter Specifies which a filter behavior; if it is an array, the first value can be a boolean, indicating whether the filter array is intended for differentiation (true) or intersection (false)
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var boolFilter = function(item, filter) {
|
||||
//A "false" filter means we return an empty copy of item
|
||||
if (filter === false){
|
||||
return (item instanceof Array) ? [] : {};
|
||||
}
|
||||
|
||||
if (filter instanceof Array && filter.length) {
|
||||
if (typeof filter[0] === 'boolean') {
|
||||
if (filter.length == 1 && typeof(filter[0]) === 'boolean') {
|
||||
//There is a filter array, but its only a single boolean
|
||||
if (filter[0] === true) {
|
||||
return copy(item);
|
||||
} else {
|
||||
return (item instanceof Array) ? [] : {};
|
||||
}
|
||||
} else {
|
||||
//The filter operation has been set explicitly; true = difference
|
||||
if (filter[0] === true) {
|
||||
return boolDiff(item, filter.slice(1));
|
||||
} else {
|
||||
return boolInter(item, filter.slice(1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//There is no explicit operation on the filter, meaning it defaults to an intersection
|
||||
return boolInter(item, filter);
|
||||
}
|
||||
} else {
|
||||
return copy(item);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that a FilterList type input is converted into its shorthand array form
|
||||
* @param {boolean|FilterList} filterList The FilterList, or boolean, that will converted into the shorthand form
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toShorthand = function(filterList) {
|
||||
var outputArray;
|
||||
if (typeof filterList === 'boolean') {
|
||||
return filterList;
|
||||
} else if (typeof filterList === 'object' && filterList !== null) {
|
||||
if (filterList instanceof Array) {
|
||||
return filterList.filter(function(v, i){
|
||||
return typeof v === 'string' || (i === 0 && v === true) ? true : false;
|
||||
});
|
||||
} else {
|
||||
if (!(filterList.values instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outputArray = filterList.values.filter(function(v){
|
||||
return typeof v === 'string' ? true : false;
|
||||
});
|
||||
|
||||
if (!outputArray.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterList.exclude) {
|
||||
outputArray.unshift(filterList.exclude);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
} else if (filterList) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the supplied string value is a relative path, and convert it to an absolute one if necessary; the segment processing paths leading with "../" was inspired by: http://stackoverflow.com/a/14780463/2230156
|
||||
* @param {string} value The value that might be a relative path, and would thus need conversion
|
||||
* @param {Object} origin The origin URL from which to which non-absolute paths are relative
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toAbsolute = function(value, origin) {
|
||||
var protocol, stack, parts;
|
||||
//Sometimes, we get lucky and the DOM Node we're working on already has the absolute URL as a DOM property, so we can just use that
|
||||
/*if (node[name]){
|
||||
//We can just grab the compiled URL directly from the DOM element - easy peasy
|
||||
var sub = node[name].indexOf(value);
|
||||
if (sub !== -1) {
|
||||
return node[name];
|
||||
}
|
||||
}*/
|
||||
|
||||
//Check to make sure we don't already have an absolute path, or even a dataURI
|
||||
if ( value.match(/(?:^data\:|^[\w\-\+\.]*?\:\/\/|^\/\/)/i) ){
|
||||
return value;
|
||||
}
|
||||
|
||||
//If we are using the root URL, start from there
|
||||
if ( value.charAt(0) === '/' ){
|
||||
return origin + value.substr(1);
|
||||
}
|
||||
|
||||
//Uh-oh, the relative path is leading with a single or double dot ("./" or "../"); things get a bit harder...
|
||||
protocol = origin.indexOf('://') > -1 ? origin.substring(0, origin.indexOf('://') + 3) : '';
|
||||
stack = (protocol.length ? origin.substring(protocol.length) : origin).split('/');
|
||||
parts = value.split('/');
|
||||
|
||||
//The value after the last slash is ALWAYS considered a filename, not a directory, so always have trailing slashes on paths ending at directories!
|
||||
stack.pop();
|
||||
|
||||
//Cycle through the relative path, changing the stack as we go
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (parts[i] == '.') {
|
||||
continue;
|
||||
}
|
||||
if (parts[i] == '..') {
|
||||
if (stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
} else {
|
||||
stack.push(parts[i]);
|
||||
}
|
||||
}
|
||||
return (protocol + stack.join('/'));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a copy of a node's properties, ignoring nasty things like event handles and functions
|
||||
* @param {Node} node The DOM Node whose properties will be copied
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var copyJSON = function(node, opts) {
|
||||
var copy = {};
|
||||
//Copy all of the node's properties
|
||||
for (var n in node){
|
||||
//Make sure this property can be accessed
|
||||
try {
|
||||
//accessing `selectionDirection`, `selectionStart`, or `selectionEnd` throws in WebKit-based browsers
|
||||
node[n];
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
//Make sure this is an own property, and isn't a live javascript function for security reasons
|
||||
if (typeof node[n] !== 'undefined' && typeof node[n] !== 'function' && n.charAt(0).toLowerCase() === n.charAt(0)) {
|
||||
//Only allowed objects are arrays
|
||||
if ( typeof node[n] !== 'object' || node[n] instanceof Array ) {
|
||||
//If we are eliminating empty fields, make sure this value is not NULL or UNDEFINED
|
||||
if (opts.cull) {
|
||||
if (node[n] || node[n] === 0 || node[n] === false) {
|
||||
copy[n] = node[n];
|
||||
}
|
||||
} else {
|
||||
copy[n] = node[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copy = boolFilter(copy, opts.domProperties);
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert the attributes property of a DOM Node to a JSON ready object
|
||||
* @param {Node} node The DOM Node whose attributes will be copied
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var attrJSON = function(node, opts) {
|
||||
var attributes = {};
|
||||
var attr = node.attributes;
|
||||
var length = attr.length;
|
||||
var absAttr;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
attributes[attr[i].name] = attr[i].value;
|
||||
}
|
||||
attributes = opts.attributes ? boolFilter(attributes, opts.attributes) : null;
|
||||
|
||||
//Add the attributes object, converting any specified absolute paths along the way
|
||||
absAttr = boolFilter(attributes, opts.absolutePaths);
|
||||
for (var i in absAttr) {
|
||||
attributes[i] = toAbsolute(absAttr[i], opts.absoluteBase);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Grab a DOM Node's computed style
|
||||
* @param {Node} node The DOM Node whose computed style will be calculated
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var styleJSON = function(node, opts) {
|
||||
//Grab the computed style
|
||||
var style, css = {};
|
||||
if (opts.computedStyle && node.style instanceof CSSStyleDeclaration) {
|
||||
style = win.getComputedStyle(node);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Get the relevant properties from the computed style
|
||||
for (var k in style) {
|
||||
if ( k !== 'cssText' && !k.match(/\d/) && typeof style[k] === 'string' && style[k].length ) {
|
||||
//css.push(k+ ': ' +style[k]+ ';');
|
||||
css[k] = style[k];
|
||||
}
|
||||
}
|
||||
|
||||
//Filter the style object
|
||||
return (opts.computedStyle instanceof Array) ? boolFilter(css, opts.computedStyle) : css;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert a single DOM Node into a simple object
|
||||
* @param {Node} node The DOM Node that will be converted
|
||||
* @param {Object} [opts] The options object passed down from the .toJSON() method; includes all options, even those not relevant to this function
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toJSON = function(node, opts, depth) {
|
||||
var style, kids, kidCount, thisChild, children, copy = copyJSON(node, opts);
|
||||
|
||||
//Per default, some tags are not allowed
|
||||
if (node.nodeType === 1) {
|
||||
if (!opts.allowDangerousElements) {
|
||||
for (var b in banned) {
|
||||
if (node.tagName.toLowerCase() === banned[b]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.nodeType === 3 && !node.nodeValue.trim()) {
|
||||
//Ignore empty buffer text nodes
|
||||
return null;
|
||||
}
|
||||
|
||||
//Copy all attributes and styles, if allowed
|
||||
if (opts.attributes && node.attributes) {
|
||||
copy.attributes = attrJSON(node, opts);
|
||||
}
|
||||
if (opts.computedStyle && (style = styleJSON(node, opts))) {
|
||||
copy.style = style;
|
||||
}
|
||||
|
||||
//Should we continue iterating?
|
||||
if (opts.deep === true || (typeof opts.deep === 'number' && opts.deep > depth)) {
|
||||
//We should!
|
||||
children = [];
|
||||
kids = (opts.htmlOnly) ? node.children : node.childNodes;
|
||||
kidCount = kids.length;
|
||||
for (var c = 0; c < kidCount; c++) {
|
||||
thisChild = toJSON(kids[c], opts, depth + 1);
|
||||
if (thisChild) {
|
||||
children.push(thisChild);
|
||||
}
|
||||
}
|
||||
|
||||
//Append the children in the appropriate place
|
||||
copy.childNodes = children;
|
||||
}
|
||||
return copy;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Take a DOM node and convert it to simple object literal (or JSON string) with no circular references and no functions or events
|
||||
* @param {Node} node The actual DOM Node which will be the starting point for parsing the DOM Tree
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @param {boolean} [opts.allowDangerousElements=`false`] Use `true` to parse the potentially dangerous elements `<link>` and `<script>`
|
||||
* @param {boolean|FilterList} [opts.absolutePaths=`'action', 'data', 'href', 'src'`] Only relevant if `opts.attributes` is not `false`; use `true` to convert all relative paths found in attribute values to absolute paths, or specify a `FilterList` of keys to boolean search
|
||||
* @param {boolean|FilterList} [opts.attributes=`true`] Use `true` to copy all attribute key-value pairs, or specify a `FilterList` of keys to boolean search
|
||||
* @param {boolean|FilterList} [opts.computedStyle=`false`] Use `true` to parse the results of "window.getComputedStyle()" on every node (specify a `FilterList` of CSS properties to be included via boolean search); this operation is VERY costly performance-wise!
|
||||
* @param {boolean} [opts.cull=`false`] Use `true` to ignore empty element properties
|
||||
* @param {boolean|number} [opts.deep=`true`] Use `true` to iterate and copy all childNodes, or an INTEGER indicating how many levels down the DOM tree to iterate
|
||||
* @param {boolean|FilterList} [opts.domProperties=true] 'false' means only 'tagName', 'nodeType', and 'nodeValue' properties will be copied, while a `FilterList` can specify DOM properties to include or exclude in the output (except for ones which serialize the DOM Node, which are handled separately by `opts.serialProperties`)
|
||||
* @param {boolean} [opts.htmlOnly=`false`] Use `true` to only iterate through childNodes where nodeType = 1 (aka, instances of HTMLElement); irrelevant if `opts.deep` is `true`
|
||||
* @param {boolean} [opts.metadata=`false`] Output a special object of the domJSON class, which includes metadata about this operation
|
||||
* @todo {boolean|FilterList} [opts.parse=`false`] a `FilterList` of properties that are DOM nodes, but will still be copied **PLANNED**
|
||||
* @param {boolean|FilterList} [opts.serialProperties=`true`] Use `true` to ignore the properties that store a serialized version of this DOM Node (ex: outerHTML, innerText, etc), or specify a `FilterList` of serial properties (no boolean search!)
|
||||
* @param {boolean} [opts.stringify=`false`] Output a JSON string, or just a JSON-ready javascript object?
|
||||
* @return {Object|string} A JSON-friendly object, or JSON string, of the DOM node -> JSON conversion output
|
||||
* @method
|
||||
* @memberof domJSON
|
||||
*/
|
||||
domJSON.toJSON = function(node, opts) {
|
||||
var copy, keys = [], options = {}, output = {};
|
||||
var timer = new Date().getTime();
|
||||
var requiring = required.slice();
|
||||
var ignoring = ignored.slice();
|
||||
|
||||
//Update the default options w/ the user's custom settings
|
||||
options = extend({}, defaultsForToJSON, opts);
|
||||
|
||||
//Convert all options that accept FilterList type inputs into the shorthand notation
|
||||
options.absolutePaths = toShorthand(options.absolutePaths);
|
||||
options.attributes = toShorthand(options.attributes);
|
||||
options.computedStyle = toShorthand(options.computedStyle);
|
||||
options.domProperties = toShorthand(options.domProperties);
|
||||
options.serialProperties = toShorthand(options.serialProperties);
|
||||
|
||||
//Make sure there is a base URL for absolute path conversions
|
||||
options.absoluteBase = win.location.origin + '/';
|
||||
|
||||
//Make lists of which DOM properties to skip and/or which are absolutely necessary
|
||||
if (options.serialProperties !== true) {
|
||||
if (options.serialProperties instanceof Array && options.serialProperties.length) {
|
||||
if (options.serialProperties[0] === true) {
|
||||
ignoring = ignoring.concat( boolDiff(serials, options.serialProperties) );
|
||||
} else {
|
||||
ignoring = ignoring.concat( boolInter(serials, options.serialProperties) );
|
||||
}
|
||||
} else {
|
||||
ignoring = ignoring.concat( serials );
|
||||
}
|
||||
}
|
||||
if (options.domProperties instanceof Array) {
|
||||
if (options.domProperties[0] === true) {
|
||||
options.domProperties = boolDiff( unique(options.domProperties, ignoring), requiring );
|
||||
} else {
|
||||
options.domProperties = boolDiff( unique(options.domProperties, requiring), ignoring );
|
||||
}
|
||||
} else {
|
||||
if (options.domProperties === false) {
|
||||
options.domProperties = requiring;
|
||||
} else {
|
||||
options.domProperties = [true].concat(ignoring);
|
||||
}
|
||||
}
|
||||
|
||||
//Transform the node into an object literal
|
||||
copy = toJSON(node, options, 0);
|
||||
|
||||
//Wrap our copy object in a nice object of its own to save some metadata
|
||||
if (options.metadata) {
|
||||
output.meta = extend({}, metadata, {
|
||||
clock: new Date().getTime() - timer,
|
||||
date: new Date().toISOString(),
|
||||
dimensions: {
|
||||
inner: {
|
||||
x: window.innerWidth,
|
||||
y: window.innerHeight
|
||||
},
|
||||
outer: {
|
||||
x: window.outerWidth,
|
||||
y: window.outerHeight
|
||||
}
|
||||
},
|
||||
options: options
|
||||
});
|
||||
output.node = copy;
|
||||
} else {
|
||||
output = copy;
|
||||
}
|
||||
|
||||
//If opts.stringify is true, turn the output object into a JSON string
|
||||
if (options.stringify) {
|
||||
return JSON.stringify(output);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a node based on a given nodeType
|
||||
* @param {number} type The type of DOM Node (only the integers 1, 3, 7, 8, 9, 10, 11 are valid, see https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType); currently, only nodeTypes 1,3, and 11 have been tested and are officially supported
|
||||
* @param {DocumentFragment} doc The document fragment to which this newly created DOM Node will be added
|
||||
* @param {Object} data The saved DOM properties that are part of the JSON representation of this DOM Node
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var createNode = function(type, doc, data) {
|
||||
if (doc instanceof DocumentFragment) {
|
||||
doc = doc.ownerDocument;
|
||||
}
|
||||
switch(type) {
|
||||
case 1: //HTMLElement
|
||||
if (typeof data.tagName === 'string') {
|
||||
return doc.createElement(data.tagName);
|
||||
}
|
||||
return false;
|
||||
|
||||
case 3: //Text Node
|
||||
if (typeof data.nodeValue === 'string' && data.nodeValue.length) {
|
||||
return doc.createTextNode(data.nodeValue);
|
||||
}
|
||||
return doc.createTextNode('');
|
||||
|
||||
case 7: //Processing Instruction
|
||||
if (data.hasOwnProperty('target') && data.hasOwnProperty('data')) {
|
||||
return doc.createProcessingInstruction(data.target, data.data);
|
||||
}
|
||||
return false;
|
||||
|
||||
case 8: //Comment Node
|
||||
if (typeof data.nodeValue === 'string') {
|
||||
return doc.createComment(data.nodeValue);
|
||||
}
|
||||
return doc.createComment('');
|
||||
|
||||
case 9: //HTML Document
|
||||
return doc.implementation.createHTMLDocument(data);
|
||||
|
||||
case 11: //Document Fragment
|
||||
return doc;
|
||||
|
||||
default: //Failed
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//Recursively convert a JSON object generated by domJSON to a DOM Node
|
||||
/**
|
||||
* Do the work of converting a JSON object/string generated by domJSON to a DOM Node
|
||||
* @param {Object} obj The JSON representation of the DOM Node we are about to create
|
||||
* @param {HTMLElement} parent The HTML Element to which this DOM Node will be appended
|
||||
* @param {DocumentFragment} doc The document fragment to which this newly created DOM Node will be added
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
var toDOM = function(obj, parent, doc, opts) {
|
||||
//Create the node, if possible
|
||||
if (obj.nodeType) {
|
||||
//Per default, some tags are not allowed
|
||||
if (obj.nodeType === 1 && !opts.allowDangerousElements) {
|
||||
for (var b in banned) {
|
||||
if (obj.tagName.toLowerCase() === banned[b]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
var node = createNode(obj.nodeType, doc, obj);
|
||||
parent.appendChild(node);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Copy all available properties that are not arrays or objects
|
||||
for (var x in obj) {
|
||||
if (typeof obj[x] !== 'object' && x !== 'isContentEditable' && x !== 'childNodes') {
|
||||
try {
|
||||
node[x] = obj[x];
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If this is an HTMLElement, set the attributes
|
||||
var src;
|
||||
if (obj.nodeType === 1 && obj.tagName) {
|
||||
if (obj.attributes) {
|
||||
//Check for cross-origin
|
||||
/*src = obj.attributes.src ? 'src' : (obj.attributes.href ? 'href' : null);
|
||||
if (src) {
|
||||
obj.attributes[src] += ( (obj.attributes[src].indexOf('?') === -1) ? '?' : '&'+Math.random().toString(36).slice(-2)+'=' ) + Math.random().toString(36).slice(-4);
|
||||
obj.attributes.crossorigin = 'anonymous';
|
||||
//node.setAttribute('crossorigin', 'anonymous');
|
||||
}*/
|
||||
for (var a in obj.attributes) {
|
||||
node.setAttribute(a, obj.attributes[a]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Finally, if we have childNodes, recurse through them
|
||||
if (obj.childNodes && obj.childNodes.length) {
|
||||
for (var c in obj.childNodes) {
|
||||
toDOM(obj.childNodes[c], node, doc, opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Take the JSON-friendly object created by the `.toJSON()` method and rebuild it back into a DOM Node
|
||||
* @param {Object} obj A JSON friendly object, or even JSON string, of some DOM Node
|
||||
* @param {Object} [opts] A list of all method options
|
||||
* @param {boolean} [opts.allowDangerousElements=`false`] Use `true` to include the potentially dangerous elements `<link>` and `<script>`
|
||||
* @param {boolean} [opts.noMeta=`false`] `true` means that this object is not wrapped in metadata, which it makes it somewhat more difficult to rebuild properly...
|
||||
* @return {DocumentFragment} A `DocumentFragment` (nodeType 11) containing the result of unpacking the input `obj`
|
||||
* @method
|
||||
* @memberof domJSON
|
||||
*/
|
||||
domJSON.toDOM = function(obj, opts) {
|
||||
var options, node;
|
||||
//Parse the JSON string if necessary
|
||||
if (typeof obj === 'string') {
|
||||
obj = JSON.parse(obj);
|
||||
}
|
||||
//Update the default options w/ the user's custom settings
|
||||
options = extend({}, defaultsForToDOM, opts);
|
||||
|
||||
//Create a document fragment, and away we go!
|
||||
node = document.createDocumentFragment();
|
||||
if (options.noMeta) {
|
||||
toDOM(obj, node, node, options);
|
||||
} else {
|
||||
toDOM(obj.node, node, node, options);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* test-code */
|
||||
//The code below is only included for private API testing, and needs to be removed in distributed builds
|
||||
domJSON.__extend = extend;
|
||||
domJSON.__unique = unique;
|
||||
domJSON.__copy = copy;
|
||||
domJSON.__boolFilter = boolFilter;
|
||||
domJSON.__boolInter = boolInter;
|
||||
domJSON.__boolDiff = boolDiff;
|
||||
domJSON.__toShorthand = toShorthand;
|
||||
/* end-test-code */
|
||||
|
||||
return domJSON;
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
pywebview.state = (function() {
|
||||
|
||||
function createEventTargetFromJSON(jsonString) {
|
||||
var data = JSON.parse(jsonString);
|
||||
var eventTarget = new EventTarget();
|
||||
|
||||
for (var key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
eventTarget[key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return eventTarget;
|
||||
}
|
||||
var initialState = '%(state)s'
|
||||
var target = createEventTargetFromJSON(initialState);
|
||||
|
||||
return new Proxy(target, {
|
||||
get: function(obj, key) {
|
||||
var value = obj[key];
|
||||
if (typeof(value) == 'function'){
|
||||
return value.bind(obj);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
set: function(target, key, value) {
|
||||
var haltUpdate = false;
|
||||
if (key.indexOf('__pywebviewHaltUpdate__') == 0) {
|
||||
key = key.replace('__pywebviewHaltUpdate__', '');
|
||||
haltUpdate = true;
|
||||
}
|
||||
var oldValue = target[key];
|
||||
|
||||
if (oldValue === value) {
|
||||
return
|
||||
}
|
||||
|
||||
target[key] = value;
|
||||
target.dispatchEvent(new CustomEvent('change', {detail: {key: key, value: value}}))
|
||||
|
||||
if (!haltUpdate) {
|
||||
pywebview._jsApiCallback('pywebviewStateUpdate', { key: key, value: value}, (Math.random() + "").substring(2));
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteProperty: function(target, key) {
|
||||
var haltUpdate = false;
|
||||
if (key.indexOf('__pywebviewHaltUpdate__') == 0) {
|
||||
key = key.replace('__pywebviewHaltUpdate__', '');
|
||||
haltUpdate = true;
|
||||
}
|
||||
|
||||
if (key in target) {
|
||||
var oldValue = target[key];
|
||||
delete target[key];
|
||||
|
||||
if (!haltUpdate) {
|
||||
pywebview._jsApiCallback('pywebviewStateDelete', key, (Math.random() + "").substring(2));
|
||||
}
|
||||
target.dispatchEvent(new CustomEvent('delete', {detail: {key: key, value: oldValue}}))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})();
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user