(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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") : 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的更多相关文章

  1. Vue.js 和 MVVM 小细节

    MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自 ...

  2. js学习笔记:操作iframe

    iframe可以说是比较老得话题了,而且网上也基本上在说少用iframe,其原因大致为:堵塞页面加载.安全问题.兼容性问题.搜索引擎抓取不到等等,不过相对于这些缺点,iframe的优点更牛,跨域请求. ...

  3. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  4. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

  5. jquery和Js的区别和基础操作

    jqery的语法和js的语法一样,算是把js升级了一下,这两种语法可以一起使用,只不过是用jqery更加方便 一个页面想要使用jqery的话,先要引入一下jqery包,jqery包从网上下一个就可以, ...

  6. 利用snowfall.jquery.js实现爱心满屏飞

    小颖在上一篇一步一步教你用CSS画爱心中已经分享一种画爱心的方法,这次再分享一种方法用css画爱心,并利用snowfall.jquery.js实现爱心满屏飞的效果. 第一步: 利用伪元素before和 ...

  7. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  8. JS正则表达式常用总结

    正则表达式的创建 JS正则表达式的创建有两种方式: new RegExp() 和 直接字面量. //使用RegExp对象创建 var regObj = new RegExp("(^\\s+) ...

  9. 干货分享:让你分分钟学会 JS 闭包

    闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,很难从定义去理解它.因此,本文不会对闭包的概念进行大篇幅描述 ...

  10. JS核心系列:理解 new 的运行机制

    和其他高级语言一样 javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象. 但在 javascript 中,万物皆对象,为什么还要通过 ...

随机推荐

  1. 从零基础到拿到网易Java实习offer,谈谈我的学习经验

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  2. 用canvas画一个等腰三角形

    上图是代码,注意,宽高只有在canvas标签内部设置宽高,绘制的路径显示才是正常的:效果如下:

  3. Spring Security + OAuth系统环境搭建(一)

    最近在做权限管理系统的重构工作,系统基于Spring Security + OAuth架构,整体架构.技术和之前调研的结果差不多,架构调研时有在这篇博客做过简单记录“Spring Cloud微服务下的 ...

  4. HashMap source code view(1)

    前言 HashMap source code view 类注释 Hash table based implementation of the Map interface. This implement ...

  5. 【个人杂谈】MacBook Pro的使用心得

    上个月刚买的MacBook Pro,苹果就发新版了.... 从apple香港官网入手了一台MacBook Pro,带到公司,用了差不多一个月吧,这里讲讲我对MacBook的看法吧. 先声明一下两点: ...

  6. mysql中外键的特点

    mysql中外键的特点简单描述: 1.要求在从表中设置外键关系: 2.从表的外键列的类型和主表的关联列的类型要求一致或兼容,名称无要求: 3.主表的关联列必须是一个key(一般是主键或唯一键): 4. ...

  7. nginx多tomcat负载均衡

    目的 先说说我要干什么,如题:使用nginx实现多个tomcat服务器的负载均衡. nginx 大名鼎鼎,相信很多人都听过,以前感觉很厉害,用了之后发现真的很厉害.nginx可以做以下几件事: 反向代 ...

  8. Jenkins构建部署Maven项目

    1 创建新项目 2 构建maven项目 3 配置 3.1  源代码管理 svn 用户名,密码 4 配置maven打包 配置SSH 保存之后 立即构建 执行成功

  9. vue开发中vue-resource + canvas 图片压缩、上传、预览

    1.使用vue-resource上传,也可以自定义ajax上传: 2.使用<input type="file" @change="submit()" na ...

  10. OpenCV入门之获取图像的旋转角度

      在我们的日常生活中,所碰到的图像往往都有一定的倾斜.那么,如何用OpenCV来获取图像的旋转角度呢?   我们以下面的图片为例,简单介绍如何用OpenCV来获取图像的旋转角度.   可以看到,该图 ...