way.js
(function (root, factory) { if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.way = factory();
} }(this, function () { "use strict"; var way, w, tagPrefix = "way"; //////////////////////////////
// EVENT EMITTER DEFINITION //
////////////////////////////// var EventEmitter = function () { this._watchers = {};
this._watchersAll = {}; }; EventEmitter.prototype.constructor = EventEmitter; EventEmitter.prototype.watchAll = function (handler) { this._watchersAll = this._watchersAll || [];
if (!_w.contains(this._watchersAll, handler)) { this._watchersAll.push(handler); } } EventEmitter.prototype.watch = function (selector, handler) { if (!this._watchers) { this._watchers = {}; }
this._watchers[selector] = this._watchers[selector] || [];
this._watchers[selector].push(handler); } EventEmitter.prototype.findWatcherDeps = function (selector) { // Go up to look for parent watchers
// ex: if "some.nested.value" is the selector, it should also trigger for "some" var result = [];
var watchers = _w.keys(this._watchers);
watchers.forEach(function (watcher) {
if (startsWith(selector, watcher)) { result.push(watcher); }
});
return result; } EventEmitter.prototype.emitChange = function (selector /* , arguments */) { if (!this._watchers) { this._watchers = {}; } var self = this; // Send data down to the local watchers
var deps = self.findWatcherDeps(selector);
deps.forEach(function (item) {
if (self._watchers[item]) {
self._watchers[item].forEach(function (handler) {
handler.apply(self, [self.get(item)]);
});
}
}); // Send data down to the global watchers
if (!self._watchersAll || !_w.isArray(self._watchersAll)) { return; }
self._watchersAll.forEach(function (watcher) {
if (_w.isFunction(watcher)) { watcher.apply(self, [selector, self.get(selector)]); }
}); } ////////////////////
// WAY DEFINITION //
//////////////////// var WAY = function () { this.data = {};
this._bindings = {};
this.options = {
persistent: true,
timeoutInput: 50,
timeoutDOM: 500
}; }; // Inherit from EventEmitter
WAY.prototype = Object.create(EventEmitter.prototype);
WAY.constructor = WAY; //////////////////////////
// DOM METHODS CHAINING //
////////////////////////// WAY.prototype.dom = function (element) { this._element = w.dom(element).get(0);
return this; }; //////////////////////////////
// DOM METHODS: DOM -> JSON //
////////////////////////////// WAY.prototype.toStorage = function (options, element) { var self = this,
element = element || self._element,
options = options || self.dom(element).getOptions(),
data = self.dom(element).toJSON(options),
scope = self.dom(element).scope(),
selector = scope ? scope + "." + options.data : options.data; if (options.readonly) { return false; }
self.set(selector, data, options); } WAY.prototype.toJSON = function (options, element) { var self = this,
element = element || self._element,
data = self.dom(element).getValue(),
options = options || self.dom(element).getOptions(); if (_w.isArray(options.pick)) { data = selectNested(data, options.pick, true); }
if (_w.isArray(options.omit)) { data = selectNested(data, options.omit, false); } return data; } //////////////////////////////
// DOM METHODS: JSON -> DOM //
////////////////////////////// WAY.prototype.fromStorage = function (options, element) { var self = this,
element = element || self._element,
options = options || self.dom(element).getOptions(); if (options.writeonly) { return false; } var scope = self.dom(element).scope(),
selector = scope ? scope + "." + options.data : options.data,
data = self.get(selector); self.dom(element).fromJSON(data, options); } WAY.prototype.fromJSON = function (data, options, element) { var self = this,
element = element || self._element,
options = options || self.dom(element).getOptions(); if (options.writeonly) { return false; } if (_w.isObject(data)) {
if (_w.isArray(options.pick)) { data = selectNested(data, options.pick, true); }
if (_w.isArray(options.omit)) { data = selectNested(data, options.omit, false); }
var currentData = _w.isObject(self.dom(element).toJSON()) ? self.dom(element).toJSON() : {};
data = _w.extend(currentData, data);
} if (options.json) { data = _json.isStringified(data) ? data : _json.prettyprint(data); } self.dom(element).setValue(data, options); } /////////////////////////////////
// DOM METHODS: GET - SET HTML //
///////////////////////////////// WAY.prototype.getValue = function (element) { var self = this,
element = element || self._element; var getters = {
"SELECT": function () {
return w.dom(element).val();
},
"INPUT": function () {
var type = w.dom(element).type();
if (_w.contains(["text", "password"], type)) {
return w.dom(element).val();
}
if (_w.contains(["checkbox", "radio"], type)) {
return w.dom(element).prop("checked") ? w.dom(element).val() : null;
} },
"TEXTAREA": function () {
return w.dom(element).val();
}
}
var defaultGetter = function (a) {
return w.dom(element).html();
} var elementType = w.dom(element).get(0).tagName;
var getter = getters[elementType] || defaultGetter;
return getter(); } WAY.prototype._transforms = {
uppercase: function (data) {
return _w.isString(data) ? data.toUpperCase() : data;
},
lowercase: function (data) {
return _w.isString(data) ? data.toLowerCase() : data;
},
reverse: function (data) {
return data && data.split && _w.isFunction(data.split) ? data.split("").reverse().join("") : data;
}
}; WAY.prototype.registerTransform = function (name, transform) {
var self = this;
if (_w.isFunction(transform)) { self._transforms[name] = transform; }
} WAY.prototype.setValue = function (data, options, element) { var self = this,
element = element || self._element,
options = options || self.dom(element).getOptions(); options.transform = options.transform || [];
options.transform.forEach(function (transformName) {
var transform = self._transforms[transformName] || function (data) { return data };
data = transform(data);
}); var setters = { "SELECT": function (a) {
w.dom(element).val(a);
},
"INPUT": function (a) {
if (!_w.isString(a)) { a = JSON.stringify(a); }
var type = w.dom(element).get(0).type;
if (_w.contains(["text", "password"], type)) {
w.dom(element).val(a || "");
}
if (_w.contains(["checkbox", "radio"], type)) {
if (a === w.dom(element).val()) {
w.dom(element).prop("checked", true);
} else {
w.dom(element).prop("checked", false);
}
}
},
"TEXTAREA": function (a) {
if (!_w.isString(a)) { a = JSON.stringify(a); }
w.dom(element).val(a || "");
},
"PRE": function (a) {
if (options.html) {
w.dom(element).html(a);
} else {
w.dom(element).text(a);
}
},
"IMG": function (a) { if (!a) {
a = options.default || "";
w.dom(element).attr("src", a);
return false;
} var isValidImageUrl = function (url, cb) {
w.dom(element).addClass("way-loading");
w.dom("img", {
src: url,
onerror: function () { cb(false); },
onload: function () { cb(true); }
});
} isValidImageUrl(a, function (response) {
w.dom(element).removeClass("way-loading");
if (response) {
w.dom(element).removeClass("way-error").addClass("way-success");
} else {
if (a) {
w.dom(element).addClass("way-error");
} else {
w.dom(element).removeClass("way-error").removeClass("way-success");
}
a = options.default || "";
}
w.dom(element).attr("src", a);
}); } }
var defaultSetter = function (a) { if (options.html) {
w.dom(element).html(a);
} else {
w.dom(element).text(a);
} } var elementType = w.dom(element).get(0).tagName;
var setter = setters[elementType] || defaultSetter;
setter(data); } WAY.prototype.setDefault = function (force, options, element) { var self = this,
element = element || self._element,
force = force || false,
options = options ? _w.extend(self.dom(element).getOptions(), options) : self.dom(element).getOptions(); // Should we just set the default value in the DOM, or also in the datastore?
if (!options.default) { return false; }
if (force) {
self.set(options.data, options.default, options);
} else {
self.dom(element).setValue(options.default, options);
} } WAY.prototype.setDefaults = function () { var self = this,
dataSelector = "[" + tagPrefix + "-default]"; var elements = w.dom(dataSelector).get();
for (var i in elements) {
var element = elements[i],
options = self.dom(element).getOptions(),
selector = options.data || null,
data = selector ? self.get(selector) : null;
if (!data) { self.dom(element).setDefault(); }
} } /////////////////////////////////////
// DOM METHODS: GET - SET BINDINGS //
///////////////////////////////////// // Scans the DOM to look for new bindings
WAY.prototype.registerBindings = function () { // Dealing with bindings removed from the DOM by just resetting all the bindings all the time.
// Isn't there a better way?
// One idea would be to add a "way-bound" class to bound elements
// self._bindings = {}; var self = this;
var selector = "[" + tagPrefix + "-data]";
self._bindings = {}; var elements = w.dom(selector).get();
for (var i in elements) {
var element = elements[i],
options = self.dom(element).getOptions(),
scope = self.dom(element).scope(),
selector = scope ? scope + "." + options.data : options.data; self._bindings[selector] = self._bindings[selector] || [];
if (!_w.contains(self._bindings[selector], w.dom(element).get(0))) {
self._bindings[selector].push(w.dom(element).get(0));
} } } WAY.prototype.updateBindings = function (selector) { var self = this;
self._bindings = self._bindings || {}; // Set bindings for the data selector
var bindings = pickAndMergeParentArrays(self._bindings, selector);
bindings.forEach(function (element) {
var focused = (w.dom(element).get(0) === w.dom(":focus").get(0)) ? true : false;
if (!focused) { self.dom(element).fromStorage(); }
}); // Set bindings for the global selector
if (self._bindings["__all__"]) {
self._bindings["__all__"].forEach(function (element) {
self.dom(element).fromJSON(self.data);
});
} } ////////////////////////////////////
// DOM METHODS: GET - SET REPEATS //
//////////////////////////////////// WAY.prototype.registerRepeats = function () { // Register repeats
var self = this;
var selector = "[" + tagPrefix + "-repeat]";
self._repeats = self._repeats || {};
self._repeatsCount = self._repeatsCount || 0; var elements = w.dom(selector).get();
for (var i in elements) {
var element = elements[i],
options = self.dom(element).getOptions(); self._repeats[options.repeat] = self._repeats[options.repeat] || []; var wrapperAttr = tagPrefix + "-repeat-wrapper=\"" + self._repeatsCount + "\"",
parent = w.dom(element).parent("[" + wrapperAttr + "]");
if (!parent.length) { self._repeats[options.repeat].push({
id: self._repeatsCount,
element: w.dom(element).clone(true).removeAttr(tagPrefix + "-repeat").removeAttr(tagPrefix + "-filter").get(0),
selector: options.repeat,
filter: options.filter
}); var wrapper = document.createElement("div");
w.dom(wrapper).attr(tagPrefix + "-repeat-wrapper", self._repeatsCount);
w.dom(wrapper).attr(tagPrefix + "-scope", options.repeat);
if (options.filter) { w.dom(wrapper).attr(tagPrefix + "-filter", options.filter); } w.dom(element).replaceWith(wrapper);
self.updateRepeats(options.repeat); self._repeatsCount++; } } } /*
WAY.prototype._filters = {
noFalsy: function(item ) {
if (!item) {
return false;
} else {
return true;
}
}
}; WAY.prototype.registerFilter = function(name, filter) {
var self = this;
if (_w.isFunction(filter)) { self._filters[name] = filter; }
}
*/ WAY.prototype.updateRepeats = function (selector) { var self = this;
self._repeats = self._repeats || {}; var repeats = pickAndMergeParentArrays(self._repeats, selector); repeats.forEach(function (repeat) { var wrapper = "[" + tagPrefix + "-repeat-wrapper=\"" + repeat.id + "\"]",
data = self.get(repeat.selector),
items = []; repeat.filter = repeat.filter || [];
w.dom(wrapper).empty(); for (var key in data) { /*
var item = data[key],
test = true;
for (var i in repeat.filter) {
var filterName = repeat.filter[i];
var filter = self._filters[filterName] || function(data) { return data };
test = filter(item);
if (!test) { break; }
}
if (!test) { continue; }
*/ w.dom(repeat.element).attr(tagPrefix + "-scope", key);
var html = w.dom(repeat.element).get(0).outerHTML;
html = html.replace(/\$\$key/gi, key);
items.push(html); } w.dom(wrapper).html(items.join(""));
self.registerBindings();
self.updateBindings(); }); } ////////////////////////
// DOM METHODS: FORMS //
//////////////////////// WAY.prototype.updateForms = function () { // If we just parse the forms with form2js (see commits before 08/19/2014) and set the data with way.set(),
// we reset the entire data for this pathkey in the datastore. It causes the bug
// reported here: https://github.com/gwendall/way.js/issues/10
// Solution:
// 1. watch new forms with a [way-data] attribute
// 2. remove this attribute
// 3. attach the appropriate attributes to its child inputs
// -> so that each input is set separately to way.js' datastore var self = this;
var selector = "form[" + tagPrefix + "-data]"; var elements = w.dom(selector).get();
for (var i in elements) { var form = elements[i],
options = self.dom(form).getOptions(),
formDataSelector = options.data;
w.dom(form).removeAttr(tagPrefix + "-data"); // Reverse needed to set the right index for "[]" names
var inputs = w.dom(form).find("[name]").reverse().get();
for (var i in inputs) { var input = inputs[i],
name = w.dom(input).attr("name"); if (endsWith(name, "[]")) {
var array = name.split("[]")[0],
arraySelector = "[name^='" + array + "']",
arrayIndex = w.dom(form).find(arraySelector).get().length;
name = array + "." + arrayIndex;
}
var selector = formDataSelector + "." + name;
options.data = selector;
self.dom(input).setOptions(options);
w.dom(input).removeAttr("name"); } } } /////////////////////////////////////////////
// DOM METHODS: GET - SET ALL DEPENDENCIES //
///////////////////////////////////////////// WAY.prototype.registerDependencies = function () { this.registerBindings();
this.registerRepeats(); } WAY.prototype.updateDependencies = function (selector) { this.updateBindings(selector);
this.updateRepeats(selector);
this.updateForms(selector); } //////////////////////////////////
// DOM METHODS: OPTIONS PARSING //
////////////////////////////////// WAY.prototype.setOptions = function (options, element) { var self = this,
element = self._element || element; for (var k in options) {
var attr = tagPrefix + "-" + k,
value = options[k];
w.dom(element).attr(attr, value);
} } WAY.prototype.getOptions = function (element) { var self = this,
element = element || self._element,
defaultOptions = {
data: null,
html: false,
readonly: false,
writeonly: false,
persistent: false
};
return _w.extend(defaultOptions, self.dom(element).getAttrs(tagPrefix)); } WAY.prototype.getAttrs = function (prefix, element) { var self = this,
element = element || self._element; var parseAttrValue = function (key, value) { var attrTypes = {
pick: "array",
omit: "array",
readonly: "boolean",
writeonly: "boolean",
json: "boolean",
html: "boolean",
persistent: "boolean"
}; var parsers = {
array: function (value) {
return value.split(",");
},
boolean: function (value) {
if (value === "true") { return true; }
if (value === "false") { return false; }
return true;
}
};
var defaultParser = function () { return value; };
var valueType = attrTypes[key] || null;
var parser = parsers[valueType] || defaultParser; return parser(value); } var attributes = {};
var attrs = [].slice.call(w.dom(element).get(0).attributes);
attrs.forEach(function (attr) {
var include = (prefix && startsWith(attr.name, prefix + "-")) ? true : false;
if (include) {
var name = (prefix) ? attr.name.slice(prefix.length + 1, attr.name.length) : attr.name;
var value = parseAttrValue(name, attr.value);
if (_w.contains(["transform", "filter"], name)) { value = value.split("|"); }
attributes[name] = value;
}
}); return attributes; } //////////////////////////
// DOM METHODS: SCOPING //
////////////////////////// WAY.prototype.scope = function (options, element) { var self = this,
element = element || self._element,
scopeAttr = tagPrefix + "-scope",
scopeBreakAttr = tagPrefix + "-scope-break",
scopes = [],
scope = ""; var parentsSelector = "[" + scopeBreakAttr + "], [" + scopeAttr + "]";
var elements = w.dom(element).parents(parentsSelector).get();
for (var i in elements) {
var el = elements[i];
if (w.dom(el).attr(scopeBreakAttr)) { break; }
var attr = w.dom(el).attr(scopeAttr);
scopes.unshift(attr);
}
if (w.dom(element).attr(scopeAttr)) { scopes.push(w.dom(element).attr(scopeAttr)); }
if (w.dom(element).attr(scopeBreakAttr)) { scopes = []; } scope = _w.compact(scopes).join("."); return scope; } //////////////////
// DATA METHODS //
////////////////// WAY.prototype.get = function (selector) { var self = this;
if (selector !== undefined && !_w.isString(selector)) { return false; }
if (!self.data) { return {}; }
return selector ? _json.get(self.data, selector) : self.data; } WAY.prototype.set = function (selector, value, options) { if (!selector) { return false; }
if (selector.split(".")[0] === "this") {
console.log("Sorry, \"this\" is a reserved word in way.js");
return false;
} var self = this;
options = options || {}; if (selector) { if (!_w.isString(selector)) { return false; }
self.data = self.data || {};
self.data = selector ? _json.set(self.data, selector, value) : {}; self.updateDependencies(selector);
self.emitChange(selector, value);
if (options.persistent) { self.backup(selector); }
} } WAY.prototype.push = function (selector, value, options) { if (!selector) { return false; } var self = this;
options = options || {}; if (selector) {
self.data = selector ? _json.push(self.data, selector, value, true) : {};
} self.updateDependencies(selector);
self.emitChange(selector, null);
if (options.persistent) { self.backup(selector); } } WAY.prototype.remove = function (selector, options) { var self = this;
options = options || {}; if (selector) {
self.data = _json.remove(self.data, selector);
} else {
self.data = {};
} self.updateDependencies(selector);
self.emitChange(selector, null);
if (options.persistent) { self.backup(selector); } } WAY.prototype.clear = function () { this.remove(null, { persistent: true }); } //////////////////////////
// LOCALSTORAGE METHODS //
////////////////////////// WAY.prototype.backup = function () { var self = this;
if (!self.options.persistent) { return; }
try {
var data = self.data || {};
localStorage.setItem(tagPrefix, JSON.stringify(data));
} catch (e) {
console.log("Your browser does not support localStorage.");
} } WAY.prototype.restore = function () { var self = this;
if (!self.options.persistent) { return; }
try {
var data = localStorage.getItem(tagPrefix);
try {
data = JSON.parse(data);
for (var key in data) {
self.set(key, data[key]);
}
} catch (e) { }
} catch (e) {
console.log("Your browser does not support localStorage.");
} } //////////
// MISC //
////////// var matchesSelector = function (el, selector) {
var matchers = ["matches", "matchesSelector", "webkitMatchesSelector", "mozMatchesSelector", "msMatchesSelector", "oMatchesSelector"],
fn = null;
for (var i in matchers) {
fn = matchers[i];
if (_w.isFunction(el[fn])) {
return el[fn](selector);
}
}
return false;
} var startsWith = function (str, starts) { if (starts === "") { return true; }
if (str === null || starts === null) { return false; }
str = String(str); starts = String(starts);
return str.length >= starts.length && str.slice(0, starts.length) === starts; } var endsWith = function (str, ends) { if (ends === "") { return true; }
if (str === null || ends === null) { return false; }
str = String(str); ends = String(ends);
return str.length >= ends.length && str.slice(str.length - ends.length, str.length) === ends; } var cleanEmptyKeys = function (object) { return _w.pick(object, _w.compact(_w.keys(object))); } var filterStartingWith = function (object, string, type) { // true: pick - false: omit var keys = _w.keys(object);
keys.forEach(function (key) {
if (type) {
if (!startsWith(key, string)) { delete object[key]; }
} else {
if (startsWith(key, string)) { delete object[key]; }
}
});
return object; } var selectNested = function (data, keys, type) { // true: pick - false: omit // Flatten / unflatten to allow for nested picks / omits (doesn't work with regular pick)
// ex: data = {something:{nested:"value"}}
// keys = ['something.nested'] var flat = _json.flatten(data);
for (var i in keys) flat = filterStartingWith(flat, keys[i], type);
var unflat = _json.unflatten(flat);
// Unflatten returns an object with an empty property if it is given an empty object
return cleanEmptyKeys(unflat); } var pickAndMergeParentArrays = function (object, selector) { // Example:
// object = { a: [1,2,3], a.b: [4,5,6], c: [7,8,9] }
// fn(object, "a.b")
// > [1,2,3,4,5,6] var keys = [];
if (selector) { // Set bindings for the specified selector // (bindings that are repeat items)
var split = selector.split("."),
lastKey = split[split.length - 1],
isArrayItem = !isNaN(lastKey); if (isArrayItem) {
split.pop();
var key = split.join(".");
keys = object[key] ? _w.union(keys, object[key]) : keys;
} // (bindings with keys starting with, to include nested bindings)
for (var key in object) {
if (startsWith(key, selector)) { keys = _w.union(keys, object[key]); }
} } else { // Set bindings for all selectors
for (var key in object) {
keys = _w.union(keys, object[key]);
} }
return keys; } var isPrintableKey = function (e) { var keycode = e.keyCode;
if (!keycode) { return true; } var valid =
(keycode === 8) || // delete
(keycode > 47 && keycode < 58) || // number keys
keycode === 32 || keycode === 13 || // spacebar & return key(s) (if you want to allow carriage returns)
(keycode > 64 && keycode < 91) || // letter keys
(keycode > 95 && keycode < 112) || // numpad keys
(keycode > 185 && keycode < 193) || // ;=,-./` (in order)
(keycode > 218 && keycode < 223); // [\]' (in order) return valid; } var escapeHTML = function (str) {
return str && _w.isString(str) ? str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") : str;
} ///////////////////////////////////////////////////
// _w (strip of the required underscore methods) //
/////////////////////////////////////////////////// var _w = {}; var ArrayProto = Array.prototype,
ObjProto = Object.prototype,
FuncProto = Function.prototype; var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind; var
push = ArrayProto.push,
slice = ArrayProto.slice,
concat = ArrayProto.concat,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty; var flatten = function (input, shallow, strict, output) {
if (shallow && _w.every(input, _w.isArray)) {
return concat.apply(output, input);
}
for (var i = 0, length = input.length; i < length; i++) {
var value = input[i];
if (!_w.isArray(value) && !_w.isArguments(value)) {
if (!strict) output.push(value);
} else if (shallow) {
push.apply(output, value);
} else {
flatten(value, shallow, strict, output);
}
}
return output;
}; var createCallback = function (func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function (value) {
return func.call(context, value);
};
case 2: return function (value, other) {
return func.call(context, value, other);
};
case 3: return function (value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function (accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function () {
return func.apply(context, arguments);
};
}; _w.compact = function (array) {
return _w.filter(array, _w.identity);
}; _w.filter = function (obj, predicate, context) {
var results = [];
if (obj == null) return results;
predicate = _w.iteratee(predicate, context);
_w.each(obj, function (value, index, list) {
if (predicate(value, index, list)) results.push(value);
});
return results;
}; _w.identity = function (value) {
return value;
}; _w.every = function (obj, predicate, context) {
if (obj == null) return true;
predicate = _w.iteratee(predicate, context);
var keys = obj.length !== +obj.length && _w.keys(obj),
length = (keys || obj).length,
index, currentKey;
for (index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
}; _w.union = function () {
return _w.uniq(flatten(arguments, true, true, []));
}; _w.uniq = function (array, isSorted, iteratee, context) {
if (array == null) return [];
if (!_w.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}
if (iteratee != null) iteratee = _w.iteratee(iteratee, context);
var result = [];
var seen = [];
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (isSorted) {
if (!i || seen !== value) result.push(value);
seen = value;
} else if (iteratee) {
var computed = iteratee(value, i, array);
if (_w.indexOf(seen, computed) < 0) {
seen.push(computed);
result.push(value);
}
} else if (_w.indexOf(result, value) < 0) {
result.push(value);
}
}
return result;
}; _w.pick = function (obj, iteratee, context) {
var result = {}, key;
if (obj == null) return result;
if (_w.isFunction(iteratee)) {
iteratee = createCallback(iteratee, context);
for (key in obj) {
var value = obj[key];
if (iteratee(value, key, obj)) result[key] = value;
}
} else {
var keys = concat.apply([], slice.call(arguments, 1));
obj = new Object(obj);
for (var i = 0, length = keys.length; i < length; i++) {
key = keys[i];
if (key in obj) result[key] = obj[key];
}
}
return result;
}; _w.has = function (obj, key) {
return obj != null && hasOwnProperty.call(obj, key);
}; _w.keys = function (obj) {
if (!_w.isObject(obj)) return [];
if (nativeKeys) return nativeKeys(obj);
var keys = [];
for (var key in obj) if (_w.has(obj, key)) keys.push(key);
return keys;
}; _w.contains = function (obj, target) {
if (obj == null) return false;
if (obj.length !== +obj.length) obj = _w.values(obj);
return _w.indexOf(obj, target) >= 0;
}; _w.sortedIndex = function (array, obj, iteratee, context) {
iteratee = _w.iteratee(iteratee, context, 1);
var value = iteratee(obj);
var low = 0, high = array.length;
while (low < high) {
var mid = low + high >>> 1;
if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
}
return low;
}; _w.property = function (key) {
return function (obj) {
return obj[key];
};
}; _w.iteratee = function (value, context, argCount) {
if (value == null) return _w.identity;
if (_w.isFunction(value)) return createCallback(value, context, argCount);
if (_w.isObject(value)) return _w.matches(value);
return _w.property(value);
}; _w.pairs = function (obj) {
var keys = _w.keys(obj);
var length = keys.length;
var pairs = Array(length);
for (var i = 0; i < length; i++) {
pairs[i] = [keys[i], obj[keys[i]]];
}
return pairs;
}; _w.matches = function (attrs) {
var pairs = _w.pairs(attrs), length = pairs.length;
return function (obj) {
if (obj == null) return !length;
obj = new Object(obj);
for (var i = 0; i < length; i++) {
var pair = pairs[i], key = pair[0];
if (pair[1] !== obj[key] || !(key in obj)) return false;
}
return true;
};
}; _w.indexOf = function (array, item, isSorted) {
if (array == null) return -1;
var i = 0, length = array.length;
if (isSorted) {
if (typeof isSorted == 'number') {
i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
} else {
i = _w.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
}
for (; i < length; i++) if (array[i] === item) return i;
return -1;
}; _w.values = function (obj) {
var keys = _w.keys(obj);
var length = keys.length;
var values = Array(length);
for (var i = 0; i < length; i++) {
values[i] = obj[keys[i]];
}
return values;
}; _w.extend = function (obj) {
if (!_w.isObject(obj)) return obj;
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
}
}
}
return obj;
}; _w.isArray = function (obj) {
return toString.call(obj) === '[object Array]';
}; _w.isBoolean = function (obj) {
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
}; _w.isUndefined = function (obj) {
return obj === void 0;
}; _w.isObject = function (obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}; _w.each = function (obj, iteratee, context) {
if (obj == null) return obj;
iteratee = createCallback(iteratee, context);
var i, length = obj.length;
if (length === +length) {
for (i = 0; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
var keys = _w.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
}; _w.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function (name) {
_w['is' + name] = function (obj) {
return toString.call(obj) === '[object ' + name + ']';
};
}); ///////////////////////////////////////////////////////////
// _json (strip of the required underscore.json methods) //
/////////////////////////////////////////////////////////// var deepJSON = function (obj, key, value, remove) { var keys = key.replace(/\[(["']?)([^\1]+?)\1?\]/g, '.$2').replace(/^\./, '').split('.'),
root,
i = 0,
n = keys.length; // Set deep value
if (arguments.length > 2) { root = obj;
n--; while (i < n) {
key = keys[i++];
obj = obj[key] = _w.isObject(obj[key]) ? obj[key] : {};
} if (remove) {
if (_w.isArray(obj)) {
obj.splice(keys[i], 1);
} else {
delete obj[keys[i]];
}
} else {
obj[keys[i]] = value || "";
} value = root; // Get deep value
} else {
while ((obj = obj[keys[i++]]) != null && i < n) { };
value = i < n ? void 0 : obj;
}
if (value == null) {
value = "";
}
return value; } var _json = {} _json.VERSION = '0.1.0';
_json.debug = true; _json.exit = function (source, reason, data, value) { if (!_json.debug) return; var messages = {};
messages.noJSON = "Not a JSON";
messages.noString = "Not a String";
messages.noArray = "Not an Array";
messages.missing = "Missing argument"; var error = { source: source, data: data, value: value };
error.message = messages[reason] ? messages[reason] : "No particular reason";
console.log("Error", error);
return; } _json.is = function (json) { return (toString.call(json) == "[object Object]"); } _json.isStringified = function (string) { var test = false;
try {
test = /^[\],:{}\s]*$/.test(string.replace(/\\["\\\/bfnrtu]/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''));
} catch (e) { }
return test; } _json.get = function (json, selector) { if (json == undefined) return _json.exit("get", "missing", "json", json);
if (selector == undefined) return _json.exit("get", "missing", "selector", selector);
if (!_w.isString(selector)) return _json.exit("get", "noString", "selector", selector);
return deepJSON(json, selector); }; _json.set = function (json, selector, value) { if (json == undefined) return _json.exit("set", "missing", "json", json);
if (selector == undefined) return _json.exit("set", "missing", "selector", selector);
if (!_w.isString(selector)) return _json.exit("set", "noString", "selector", selector);
return value ? deepJSON(json, selector, value) : _json.remove(json, selector);
// return deepJSON(json, selector, value); // Now removes the property if the value is empty. Maybe should keep it instead? }; _json.remove = function (json, selector) { if (json == undefined) return _json.exit("remove", "missing", "json", json);
if (selector == undefined) return _json.exit("remove", "missing", "selector", selector);
if (!_w.isString(selector)) return _json.exit("remove", "noString", "selector", selector);
return deepJSON(json, selector, null, true); } _json.push = function (json, selector, value, force) { if (json == undefined) return _json.exit("push", "missing", "json", json);
if (selector == undefined) return _json.exit("push", "missing", "selector", selector);
var array = _json.get(json, selector);
if (!_w.isArray(array)) {
if (force) {
array = [];
} else {
return _json.exit("push", "noArray", "array", array);
}
}
array.push(value);
return _json.set(json, selector, array); } _json.unshift = function (json, selector, value) { if (json == undefined) return _json.exit("unshift", "missing", "json", json);
if (selector == undefined) return _json.exit("unshift", "missing", "selector", selector);
if (value == undefined) return _json.exit("unshift", "missing", "value", value);
var array = _json.get(json, selector);
if (!_w.isArray(array)) return _json.exit("unshift", "noArray", "array", array);
array.unshift(value);
return _json.set(json, selector, array); } _json.flatten = function (json) { if (json.constructor.name != "Object") return _json.exit("flatten", "noJSON", "json", json); var result = {};
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for (var i = 0, l = cur.length; i < l; i++) {
recurse(cur[i], prop ? prop + "." + i : "" + i);
if (l == 0) result[prop] = [];
}
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + "." + p : p);
}
if (isEmpty) result[prop] = {};
}
}
recurse(json, "");
return result; } _json.unflatten = function (data) { if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for (var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while (idx >= 0);
cur[prop] = data[p];
}
return result[""]; } _json.prettyprint = function (json) { return JSON.stringify(json, undefined, 2); } //////////////////////////////////////////
// wQuery (mini replacement for jQuery) //
////////////////////////////////////////// var wQuery = function () { };
wQuery.constructor = wQuery; wQuery.prototype.dom = function (selector, createOptions) { var self = this,
elements = []; if (createOptions) {
var element = document.createElement(selector);
for (var k in createOptions) {
element[k] = createOptions[k];
}
} else {
if (_w.isString(selector)) {
elements = [].slice.call(document.querySelectorAll(selector));
} else {
if (_w.isObject(selector) && selector.attributes) { elements = [selector]; }
}
self._elements = elements;
self.length = elements.length;
return self;
} } wQuery.prototype.on = function (events, fn) { var self = this,
elements = self._elements;
events = events.split(" ");
for (var i = 0, lenEl = elements.length; i < lenEl; i++) {
var element = elements[i];
for (var j = 0, lenEv = events.length; j < lenEv; j++) {
if (element.addEventListener) { element.addEventListener(events[j], fn, false); }
}
} } wQuery.prototype.find = function (selector) { var self = this,
element = self.get(0),
elements = []; if (_w.isString(selector)) {
elements = [].slice.call(element.querySelectorAll(selector));
}
self._elements = elements;
return self; } wQuery.prototype.get = function (index, chain) { var self = this,
elements = self._elements || [],
element = elements[index] || {}; if (chain) {
self._element = element;
return self;
} else {
return _w.isNumber(index) ? element : elements;
} } wQuery.prototype.reverse = function () {
this._elements = this._elements.reverse();
return this;
} wQuery.prototype.val = function (value) {
return this.prop("value", value);
} wQuery.prototype.type = function (value) {
return this.prop("type", value);
} wQuery.prototype.html = function (value) {
return this.prop("innerHTML", value);
} wQuery.prototype.text = function (value) {
return this.prop("innerHTML", escapeHTML(value));
} wQuery.prototype.prop = function (prop, value) { var self = this,
elements = self._elements; for (var i in elements) {
if (_w.isUndefined(value)) {
return elements[i][prop];
} else {
elements[i][prop] = value;
}
} } wQuery.prototype.attr = function (attr, value) { var self = this,
elements = self._elements;
for (var i in elements) {
if (value === undefined) {
return elements[i].getAttribute(attr);
} else {
elements[i].setAttribute(attr, value);
}
}
return self; } wQuery.prototype.removeAttr = function (attr) {
var self = this;
for (var i in self._elements) self._elements[i].removeAttribute(attr);
return self;
} wQuery.prototype.addClass = function (c) {
var self = this;
for (var i in self._elements) self._elements[i].classList.add(c);
return self;
} wQuery.prototype.removeClass = function (c) {
var self = this;
for (var i in self._elements) self._elements[i].classList.remove(c);
return self;
} wQuery.prototype.parents = function (selector) {
var self = this,
element = self.get(0),
parent = element.parentNode,
parents = []; while (parent !== null) {
var o = parent,
matches = matchesSelector(o, selector),
isNotDomRoot = (o.doctype === undefined) ? true : false;
if (!selector) { matches = true; }
if (matches && isNotDomRoot) { parents.push(o); }
parent = o.parentNode;
}
self._elements = parents;
return self;
} wQuery.prototype.parent = function (selector) {
var self = this,
element = self.get(0),
o = element.parentNode,
matches = matchesSelector(o, selector);
if (!selector) { matches = true; }
return matches ? o : {};
} wQuery.prototype.clone = function (chain) {
var self = this,
element = self.get(0),
clone = element.cloneNode(true);
self._elements = [clone];
return chain ? self : clone;
} wQuery.prototype.empty = function (chain) {
var self = this,
element = self.get(0);
if (!element || !element.hasChildNodes) { return chain ? self : element; } while (element.hasChildNodes()) {
element.removeChild(element.lastChild);
}
return chain ? self : element;
} wQuery.prototype.replaceWith = function (newDOM) {
var self = this,
oldDOM = self.get(0),
parent = oldDOM.parentNode;
parent.replaceChild(newDOM, oldDOM);
} wQuery.prototype.ready = function (callback) { if (document && _w.isFunction(document.addEventListener)) {
document.addEventListener("DOMContentLoaded", callback, false);
} else if (window && _w.isFunction(window.addEventListener)) {
window.addEventListener("load", callback, false);
} else {
document.onreadystatechange = function () {
if (document.readyState === "complete") { callback(); }
}
} } //////////////////////
// WATCH DOM EVENTS //
////////////////////// way = new WAY(); var timeoutInput = null;
var eventInputChange = function (e) {
if (timeoutInput) { clearTimeout(timeoutInput); }
timeoutInput = setTimeout(function () {
var element = w.dom(e.target).get(0);
way.dom(element).toStorage();
}, way.options.timeout);
} var eventClear = function (e) {
e.preventDefault();
var options = way.dom(this).getOptions();
way.remove(options.data, options);
} var eventPush = function (e) {
e.preventDefault();
var options = way.dom(this).getOptions();
if (!options || !options["action-push"]) { return false; }
var split = options["action-push"].split(":"),
selector = split[0] || null,
value = split[1] || null;
way.push(selector, value, options);
} var eventRemove = function (e) {
e.preventDefault();
var options = way.dom(this).getOptions();
if (!options || !options["action-remove"]) { return false; }
way.remove(options["action-remove"], options);
} var timeoutDOM = null;
var eventDOMChange = function () { // We need to register dynamically added bindings so we do it by watching DOM changes
// We use a timeout since "DOMSubtreeModified" gets triggered on every change in the DOM (even input value changes)
// so we can limit the number of scans when a user is typing something
if (timeoutDOM) { clearTimeout(timeoutDOM); }
timeoutDOM = setTimeout(function () {
way.registerDependencies();
setEventListeners();
}, way.options.timeoutDOM); } //////////////
// INITIATE //
////////////// w = new wQuery();
way.w = w; var setEventListeners = function () { w.dom("body").on("DOMSubtreeModified", eventDOMChange);
w.dom("[" + tagPrefix + "-data]").on("input change", eventInputChange);
w.dom("[" + tagPrefix + "-clear]").on("click", eventClear);
w.dom("[" + tagPrefix + "-action-remove]").on("click", eventRemove);
w.dom("[" + tagPrefix + "-action-push]").on("click", eventPush); } var eventInit = function () { setEventListeners();
way.restore();
way.setDefaults();
way.registerDependencies();
way.updateDependencies(); } w.ready(eventInit); return way; }));
修复,绑定null值。
deepJSON方法增加null判断。null 转‘’。
way.js的更多相关文章
- Vue.js 和 MVVM 小细节
MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自 ...
- js学习笔记:操作iframe
iframe可以说是比较老得话题了,而且网上也基本上在说少用iframe,其原因大致为:堵塞页面加载.安全问题.兼容性问题.搜索引擎抓取不到等等,不过相对于这些缺点,iframe的优点更牛,跨域请求. ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- JS调用Android、Ios原生控件
在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...
- jquery和Js的区别和基础操作
jqery的语法和js的语法一样,算是把js升级了一下,这两种语法可以一起使用,只不过是用jqery更加方便 一个页面想要使用jqery的话,先要引入一下jqery包,jqery包从网上下一个就可以, ...
- 利用snowfall.jquery.js实现爱心满屏飞
小颖在上一篇一步一步教你用CSS画爱心中已经分享一种画爱心的方法,这次再分享一种方法用css画爱心,并利用snowfall.jquery.js实现爱心满屏飞的效果. 第一步: 利用伪元素before和 ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
- JS正则表达式常用总结
正则表达式的创建 JS正则表达式的创建有两种方式: new RegExp() 和 直接字面量. //使用RegExp对象创建 var regObj = new RegExp("(^\\s+) ...
- 干货分享:让你分分钟学会 JS 闭包
闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概念进行大篇幅描述 ...
- JS核心系列:理解 new 的运行机制
和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...
随机推荐
- (转)python生态环境简介
Python生态环境简介 作者: Mir Nazim 原文: Python Ecosystem - An Introduction 译者: dccrazyboy 原译: Python生态环境简介 当 ...
- 我在Fackbook的这三年[转]
本周开始是我在Facebook的第四个年头.我的经验在这里发生了巨大的变化:退学后我就来到了这里,在这里遇到了前所未有的挑战.单从这方面讲,我经历和遇到的挑战比这里4/5的人都要多.所以,我想分享一些 ...
- npm link 命令解析
文字转的 对开发者而言,这算是最有价值的命令.假设我们开发了一个模块叫 test ,然后我们在 test-example 里引用这个模块 ,每次 test 模块的变动我们都需要反映到 test-exa ...
- bash 的配置文件加载顺序
bash配置文件的加载顺序和登陆方式有关,下面先介绍下登陆方式. 1 登陆方式有2种 登陆式SHELL: su - oracle su -l oracle 正常从终端登陆 非登录式SHELL: ...
- JS中如何理解浮点数?
本文由云+社区发表 相信大家在平常的 JavaScript 开发中,都有遇到过浮点数运算精度误差的问题,比如 console.log(0.1+0.2===0.3)// false.在 JavaScri ...
- Jenkins结合.net平台工具之ReportGenerator
上一节我们讲解了如何使用opencover生成单元测试覆盖率报告,opencover默认生成的report为xml格式,可读性并不是特别强,如果靠阅读opencover生成的results.xml来分 ...
- Java核心技术 对象与类
对象与对象变量: 要想使用对象,就必须首先构造对象,并指定其初始状态,然后,对对象应用方法. 在Java中,使用构造器构造新实例.构造器是一种特殊的方法,用来构造并初始化对象. 在实际开发中,通常需要 ...
- Spring Boot入门(13)自制音乐平台
经过笔者这几天的辛勤劳作(其实就是苦逼地码代码),一个新的网站已经上线啦!该网站是用Spring Boot工具写的,主要实现的功能如下: 根据歌曲名称和音乐平台搜索歌曲,并实现歌曲的在线播放: 歌 ...
- redis实现高并发下的抢购/秒杀功能
之前写过一篇文章,高并发的解决思路(点此进入查看),今天再次抽空整理下实际场景中的具体代码逻辑实现吧:抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢 ...
- mybatis XML中 遍历map写法
<select id="selectMapTest" parameterType="java.util.HashMap" resultMap=" ...