Regex to change a html element class with javascript not working - javascript

I have the following javascript function to open and close sub list elements on an onclick event:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className == "nav nav-second-level collapse in") {
subMen.className = "nav nav-second-level collapse";
} else {
subMen.className += " in";
}
}
}
The "collapse" is a css class which makes display=none hiding the sub list and "in" is a class which makes display=block showing the sub list, creating a menu with submenus.
I found in this question Change an element's class with JavaScript in the first(accepted) answer use of a regex in order to do this. I tried it like this:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
} else {
subMen.className += " in";
}
}
}
The code without the regex works perfectly but with the regex it doesn't. I checked the regex in regex101.com and it seems to work there. As I understand it's more appropriate to use the regex than a long string of all the class names and also I also have a nav-third-level class that I have to close and open so the regex seems to be the convenient and proper way to do it.
What's wrong?
Thank you.

No need of regex here. You can use classList
Using classList is a convenient alternative to accessing an element's list of classes as a space-delimited string via element.className.
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
subMen.classList.toggle('in');
}
}
toggle() will toggle the class of the element. If the element already has the class, it'll remove it, if not then toggle will add the class to the element.
Check the Browser Compatibility.
You can use following SHIM from MDN for IE9,
/*
* classList.js: Cross-browser full element.classList implementation.
* 2014-07-23
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
/*global self, document, DOMException */
/*! #source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
if ("document" in self) {
// Full polyfill for browsers with no classList support
if (!("classList" in document.createElement("_"))) {
(function (view) {
"use strict";
if (!('Element' in view)) return;
var
classListProp = "classList",
protoProp = "prototype",
elemCtrProto = view.Element[protoProp],
objCtr = Object,
strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, "");
},
arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0,
len = this.length;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
,
DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
},
checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR", "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR", "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
},
ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
i = 0,
len = classes.length;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.setAttribute("class", this.toString());
};
},
classListProto = ClassList[protoProp] = [],
classListGetter = function () {
return new ClassList(this);
};
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function () {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function () {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false,
index;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function (token, force) {
token += "";
var
result = this.contains(token),
method = result ?
force !== true && "remove" :
force !== false && "add";
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter,
enumerable: true,
configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
} else {
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function () {
"use strict";
var testElement = document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function (method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function (token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function (token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}
}
If you're using jQuery, you can use toggleClass():
function ShowHideDtls(itId) {
$('#' + itId).toggleClass('in');
}
Edit
If you still want to use regex:
if (/\bin\b/.test(subMen.className))
subMen.className.replace(/\bin\b/, '');
} else {
subMen.className += " in";
}
You can also use split() and indexOf as follow to check if a class is present on element.
var classes = className.split(/\s+/),
classIndex = classes.indexOf('in');
if (classIndex > -1) {
classes.splice(classIndex, 1);
subMen.className = classes.join(' ');
} else {
subMen.className += " in";
}

replace function returns the resultant value, it do not assign value indirectly.
So do following:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
subMen.className = subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
}
else {
subMen.className += " in";
}
}
}

Related

Array Data to HTML Table

What is the best way to get data from an array using tabletop.js into an html table? I am trying to take the data from the array and replace the dummy data that I currently have in there. The link below better explains what I'm trying to do.
Example of Data & Table
Here is my Fiddle: https://jsfiddle.net/1qeydx4f/
HTML:
<script type="text/javascript">
var publicSpreadsheetUrl = 'https://docs.google.com/spreadsheets/d/16EypE4AkZVutLblpQEpsBu4ly7ziqBRYPxlv7wO1SJs/edit?usp=sharing';
function init() {
Tabletop.init( { key: publicSpreadsheetUrl,
callback: showInfo,
simpleSheet: true } )
}
function showInfo(data, tabletop) {
console.log(data);
}
window.addEventListener('DOMContentLoaded', init)
</script>
<div class="docs-section" id="tables">
<h6 class="docs-header">3-Pointers Made (2019)</h6>
<p> </p>
<div class="docs-example">
<table class="u-full-width">
<thead>
<tr>
<th>Name</th>
<th>Team</th>
<th>Made</th>
<th>Attempted</th>
<th>Percent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dave Gamache</td>
<td>GS</td>
<td>150</td>
<td>300</td>
<td>50%</td>
</tr>
<tr>
<td>Dwayne Johnson</td>
<td>DAL</td>
<td>128</td>
<td>298</td>
<td>43%</td>
</tr>
</tbody>
</table>
</div>
</div>
Tabletop.js:
(function() {
'use strict';
var inNodeJS = typeof process !== 'undefined' && !process.browser;
var request = function requestNotProvided() {
throw new Error("The 'request' module is only available while running in Node.");
};
if(inNodeJS) { // This will get stripped out by Uglify, and Webpack will not include it
request = require('request');
}
var supportsCORS = false;
var inLegacyIE = false;
try {
var testXHR = new XMLHttpRequest();
if (typeof testXHR.withCredentials !== 'undefined') {
supportsCORS = true;
} else {
if ('XDomainRequest' in window) {
supportsCORS = true;
inLegacyIE = true;
}
}
} catch (e) { }
// Create a simple indexOf function for support
// of older browsers. Uses native indexOf if
// available. Code similar to underscores.
// By making a separate function, instead of adding
// to the prototype, we will not break bad for loops
// in older browsers
var indexOfProto = Array.prototype.indexOf;
var ttIndexOf = function(array, item) {
var i = 0, l = array.length;
if (indexOfProto && array.indexOf === indexOfProto) {
return array.indexOf(item);
}
for (; i < l; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
};
/*
Initialize with Tabletop.init( { key: '0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc' } )
OR!
Initialize with Tabletop.init( { key: 'https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc&output=html&widget=true' } )
OR!
Initialize with Tabletop.init('0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc')
*/
var Tabletop = function(options) {
// Make sure Tabletop is being used as a constructor no matter what.
if(!this || !(this instanceof Tabletop)) {
return new Tabletop(options);
}
if(typeof(options) === 'string') {
options = { key : options };
}
this.callback = options.callback;
this.wanted = options.wanted || [];
this.key = options.key;
this.simpleSheet = !!options.simpleSheet;
this.parseNumbers = !!options.parseNumbers;
this.wait = !!options.wait;
this.reverse = !!options.reverse;
this.postProcess = options.postProcess;
this.debug = !!options.debug;
this.query = options.query || '';
this.orderby = options.orderby;
this.endpoint = options.endpoint || 'https://spreadsheets.google.com';
this.singleton = !!options.singleton;
this.simpleUrl = !!(options.simpleUrl || options.simple_url); //jshint ignore:line
this.authkey = options.authkey;
this.sheetPrivacy = this.authkey ? 'private' : 'public'
this.callbackContext = options.callbackContext;
// Default to on, unless there's a proxy, in which case it's default off
this.prettyColumnNames = typeof(options.prettyColumnNames) === 'undefined' ? !options.proxy : options.prettyColumnNames;
if(typeof(options.proxy) !== 'undefined') {
// Remove trailing slash, it will break the app
this.endpoint = options.proxy.replace(/\/$/,'');
this.simpleUrl = true;
this.singleton = true;
// Let's only use CORS (straight JSON request) when
// fetching straight from Google
supportsCORS = false;
}
this.parameterize = options.parameterize || false;
if (this.singleton) {
if (typeof(Tabletop.singleton) !== 'undefined') {
this.log('WARNING! Tabletop singleton already defined');
}
Tabletop.singleton = this;
}
/* Be friendly about what you accept */
if (/key=/.test(this.key)) {
this.log('You passed an old Google Docs url as the key! Attempting to parse.');
this.key = this.key.match('key=(.*?)(&|#|$)')[1];
}
if (/pubhtml/.test(this.key)) {
this.log('You passed a new Google Spreadsheets url as the key! Attempting to parse.');
this.key = this.key.match('d\\/(.*?)\\/pubhtml')[1];
}
if(/spreadsheets\/d/.test(this.key)) {
this.log('You passed the most recent version of Google Spreadsheets url as the key! Attempting to parse.');
this.key = this.key.match('d\\/(.*?)\/')[1];
}
if (!this.key) {
this.log('You need to pass Tabletop a key!');
return;
}
this.log('Initializing with key ' + this.key);
this.models = {};
this.modelNames = [];
this.model_names = this.modelNames; //jshint ignore:line
this.baseJsonPath = '/feeds/worksheets/' + this.key + '/' + this.sheetPrivacy +'/basic?alt=';
if (inNodeJS || supportsCORS) {
this.baseJsonPath += 'json';
} else {
this.baseJsonPath += 'json-in-script';
}
if(!this.wait) {
this.fetch();
}
};
// A global storage for callbacks.
Tabletop.callbacks = {};
// Backwards compatibility.
Tabletop.init = function(options) {
return new Tabletop(options);
};
Tabletop.sheets = function() {
this.log('Times have changed! You\'ll want to use var tabletop = Tabletop.init(...); tabletop.sheets(...); instead of Tabletop.sheets(...)');
};
Tabletop.prototype = {
fetch: function(callback) {
if (typeof(callback) !== 'undefined') {
this.callback = callback;
}
this.requestData(this.baseJsonPath, this.loadSheets);
},
/*
This will call the environment appropriate request method.
In browser it will use JSON-P, in node it will use request()
*/
requestData: function(path, callback) {
this.log('Requesting', path);
if (inNodeJS) {
this.serverSideFetch(path, callback);
} else {
//CORS only works in IE8/9 across the same protocol
//You must have your server on HTTPS to talk to Google, or it'll fall back on injection
var protocol = this.endpoint.split('//').shift() || 'http';
if (supportsCORS && (!inLegacyIE || protocol === location.protocol)) {
this.xhrFetch(path, callback);
} else {
this.injectScript(path, callback);
}
}
},
/*
Use Cross-Origin XMLHttpRequest to get the data in browsers that support it.
*/
xhrFetch: function(path, callback) {
//support IE8's separate cross-domain object
var xhr = inLegacyIE ? new XDomainRequest() : new XMLHttpRequest();
xhr.open('GET', this.endpoint + path);
var self = this;
xhr.onload = function() {
var json;
try {
json = JSON.parse(xhr.responseText);
} catch (e) {
console.error(e);
}
callback.call(self, json);
};
xhr.send();
},
/*
Insert the URL into the page as a script tag. Once it's loaded the spreadsheet data
it triggers the callback. This helps you avoid cross-domain errors
http://code.google.com/apis/gdata/samples/spreadsheet_sample.html
Let's be plain-Jane and not use jQuery or anything.
*/
injectScript: function(path, callback) {
var script = document.createElement('script');
var callbackName;
if (this.singleton) {
if (callback === this.loadSheets) {
callbackName = 'Tabletop.singleton.loadSheets';
} else if (callback === this.loadSheet) {
callbackName = 'Tabletop.singleton.loadSheet';
}
} else {
var self = this;
callbackName = 'tt' + (+new Date()) + (Math.floor(Math.random()*100000));
// Create a temp callback which will get removed once it has executed,
// this allows multiple instances of Tabletop to coexist.
Tabletop.callbacks[ callbackName ] = function () {
var args = Array.prototype.slice.call( arguments, 0 );
callback.apply(self, args);
script.parentNode.removeChild(script);
delete Tabletop.callbacks[callbackName];
};
callbackName = 'Tabletop.callbacks.' + callbackName;
}
var url = path + '&callback=' + callbackName;
if (this.simpleUrl) {
// We've gone down a rabbit hole of passing injectScript the path, so let's
// just pull the sheet_id out of the path like the least efficient worker bees
if(path.indexOf('/list/') !== -1) {
script.src = this.endpoint + '/' + this.key + '-' + path.split('/')[4];
} else {
script.src = this.endpoint + '/' + this.key;
}
} else {
script.src = this.endpoint + url;
}
if (this.parameterize) {
script.src = this.parameterize + encodeURIComponent(script.src);
}
this.log('Injecting', script.src);
document.getElementsByTagName('script')[0].parentNode.appendChild(script);
},
/*
This will only run if tabletop is being run in node.js
*/
serverSideFetch: function(path, callback) {
var self = this;
this.log('Fetching', this.endpoint + path);
request({url: this.endpoint + path, json: true}, function(err, resp, body) {
if (err) {
return console.error(err);
}
callback.call(self, body);
});
},
/*
Is this a sheet you want to pull?
If { wanted: ["Sheet1"] } has been specified, only Sheet1 is imported
Pulls all sheets if none are specified
*/
isWanted: function(sheetName) {
if (this.wanted.length === 0) {
return true;
} else {
return (ttIndexOf(this.wanted, sheetName) !== -1);
}
},
/*
What gets send to the callback
if simpleSheet === true, then don't return an array of Tabletop.this.models,
only return the first one's elements
*/
data: function() {
// If the instance is being queried before the data's been fetched
// then return undefined.
if (this.modelNames.length === 0) {
return undefined;
}
if (this.simpleSheet) {
if (this.modelNames.length > 1 && this.debug) {
this.log('WARNING You have more than one sheet but are using simple sheet mode! Don\'t blame me when something goes wrong.');
}
return this.models[this.modelNames[0]].all();
} else {
return this.models;
}
},
/*
Add another sheet to the wanted list
*/
addWanted: function(sheet) {
if(ttIndexOf(this.wanted, sheet) === -1) {
this.wanted.push(sheet);
}
},
/*
Load all worksheets of the spreadsheet, turning each into a Tabletop Model.
Need to use injectScript because the worksheet view that you're working from
doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though.
Calls back to loadSheet in order to get the real work done.
Used as a callback for the worksheet-based JSON
*/
loadSheets: function(data) {
var i, ilen;
var toLoad = [];
this.googleSheetName = data.feed.title.$t;
this.foundSheetNames = [];
for (i = 0, ilen = data.feed.entry.length; i < ilen ; i++) {
this.foundSheetNames.push(data.feed.entry[i].title.$t);
// Only pull in desired sheets to reduce loading
if (this.isWanted(data.feed.entry[i].content.$t)) {
var linkIdx = data.feed.entry[i].link.length-1;
var sheetId = data.feed.entry[i].link[linkIdx].href.split('/').pop();
var jsonPath = '/feeds/list/' + this.key + '/' + sheetId + '/' + this.sheetPrivacy + '/values?alt=';
if (inNodeJS || supportsCORS) {
jsonPath += 'json';
} else {
jsonPath += 'json-in-script';
}
if (this.query) {
// Query Language Reference (0.7)
jsonPath += '&tq=' + this.query;
}
if (this.orderby) {
jsonPath += '&orderby=column:' + this.orderby.toLowerCase();
}
if (this.reverse) {
jsonPath += '&reverse=true';
}
toLoad.push(jsonPath);
}
}
this.sheetsToLoad = toLoad.length;
for(i = 0, ilen = toLoad.length; i < ilen; i++) {
this.requestData(toLoad[i], this.loadSheet);
}
},
/*
Access layer for the this.models
.sheets() gets you all of the sheets
.sheets('Sheet1') gets you the sheet named Sheet1
*/
sheets: function(sheetName) {
if (typeof sheetName === 'undefined') {
return this.models;
} else {
if (typeof(this.models[sheetName]) === 'undefined') {
// alert( "Can't find " + sheetName );
return;
} else {
return this.models[sheetName];
}
}
},
sheetReady: function(model) {
this.models[model.name] = model;
if (ttIndexOf(this.modelNames, model.name) === -1) {
this.modelNames.push(model.name);
}
this.sheetsToLoad--;
if (this.sheetsToLoad === 0) {
this.doCallback();
}
},
/*
Parse a single list-based worksheet, turning it into a Tabletop Model
Used as a callback for the list-based JSON
*/
loadSheet: function(data) {
var that = this;
new Tabletop.Model({
data: data,
parseNumbers: this.parseNumbers,
postProcess: this.postProcess,
tabletop: this,
prettyColumnNames: this.prettyColumnNames,
onReady: function() {
that.sheetReady(this);
}
});
},
/*
Execute the callback upon loading! Rely on this.data() because you might
only request certain pieces of data (i.e. simpleSheet mode)
Tests this.sheetsToLoad just in case a race condition happens to show up
*/
doCallback: function() {
if(this.sheetsToLoad === 0) {
this.callback.apply(this.callbackContext || this, [this.data(), this]);
}
},
log: function() {
if(this.debug) {
if(typeof console !== 'undefined' && typeof console.log !== 'undefined') {
Function.prototype.apply.apply(console.log, [console, arguments]);
}
}
}
};
/*
Tabletop.Model stores the attribute names and parses the worksheet data
to turn it into something worthwhile
Options should be in the format { data: XXX }, with XXX being the list-based worksheet
*/
Tabletop.Model = function(options) {
var i, j, ilen, jlen;
this.columnNames = [];
this.column_names = this.columnNames; // jshint ignore:line
this.name = options.data.feed.title.$t;
this.tabletop = options.tabletop;
this.elements = [];
this.onReady = options.onReady;
this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae
if (typeof(options.data.feed.entry) === 'undefined') {
options.tabletop.log('Missing data for ' + this.name + ', make sure you didn\'t forget column headers');
this.originalColumns = [];
this.elements = [];
this.ready();
return;
}
for (var key in options.data.feed.entry[0]){
if (/^gsx/.test(key)) {
this.columnNames.push(key.replace('gsx$',''));
}
}
this.originalColumns = this.columnNames;
this.original_columns = this.originalColumns; // jshint ignore:line
for (i = 0, ilen = options.data.feed.entry.length ; i < ilen; i++) {
var source = options.data.feed.entry[i];
var element = {};
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
var cell = source['gsx$' + this.columnNames[j]];
if (typeof(cell) !== 'undefined') {
if (options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t)) {
element[this.columnNames[j]] = +cell.$t;
} else {
element[this.columnNames[j]] = cell.$t;
}
} else {
element[this.columnNames[j]] = '';
}
}
if (element.rowNumber === undefined) {
element.rowNumber = i + 1;
}
this.elements.push(element);
}
if (options.prettyColumnNames) {
this.fetchPrettyColumns();
} else {
this.ready();
}
};
Tabletop.Model.prototype = {
/*
Returns all of the elements (rows) of the worksheet as objects
*/
all: function() {
return this.elements;
},
fetchPrettyColumns: function() {
if (!this.raw.feed.link[3]) {
return this.ready();
}
var cellurl = this.raw.feed.link[3].href.replace('/feeds/list/', '/feeds/cells/').replace('https://spreadsheets.google.com', '');
var that = this;
this.tabletop.requestData(cellurl, function(data) {
that.loadPrettyColumns(data);
});
},
beforeReady: function() {
if(this.postProcess) {
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
this.postProcess(element);
}
}
},
ready: function() {
this.beforeReady();
this.onReady.call(this);
},
/*
* Store column names as an object
* with keys of Google-formatted "columnName"
* and values of human-readable "Column name"
*/
loadPrettyColumns: function(data) {
var prettyColumns = {};
var columnNames = this.columnNames;
var i = 0;
var l = columnNames.length;
for (; i < l; i++) {
if (typeof data.feed.entry[i].content.$t !== 'undefined') {
prettyColumns[columnNames[i]] = data.feed.entry[i].content.$t;
} else {
prettyColumns[columnNames[i]] = columnNames[i];
}
}
this.prettyColumns = prettyColumns;
this.pretty_columns = this.prettyColumns; // jshint ignore:line
this.prettifyElements();
this.ready();
},
/*
* Go through each row, substitutiting
* Google-formatted "columnName"
* with human-readable "Column name"
*/
prettifyElements: function() {
var prettyElements = [],
orderedPrettyNames = [],
i, j, ilen, jlen;
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
orderedPrettyNames.push(this.prettyColumns[this.columnNames[j]]);
}
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
var newElement = {};
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
var newColumnName = this.prettyColumns[this.columnNames[j]];
newElement[newColumnName] = this.elements[i][this.columnNames[j]];
}
prettyElements.push(newElement);
}
this.elements = prettyElements;
this.columnNames = orderedPrettyNames;
},
/*
Return the elements as an array of arrays, instead of an array of objects
*/
toArray: function() {
var array = [],
i, j, ilen, jlen;
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
var row = [];
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
row.push(this.elements[i][ this.columnNames[j]]);
}
array.push(row);
}
return array;
}
};
if(typeof module !== 'undefined' && module.exports) { //don't just use inNodeJS, we may be in Browserify
module.exports = Tabletop;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return Tabletop;
});
} else {
window.Tabletop = Tabletop;
}
})();
This can actually be achieved pretty simply. All you need to do is give your table an ID to be able to select it, and have a loop in the tabletop callback:
function gotData(data) {
const table = document.getElementById("table1-id").tBodies[0];
for (const player of data) {
const row = table.insertRow(-1); // index -1 inserts at bottom
const keys = Object.keys(player);
for (let i = 0; i < keys.length; i++) {
const c = row.insertCell(i);
c.appendChild(document.createTextNode(player[keys[i]]));
}
}
}

How to stop executing Javascript function containing infinite loop after some time

Suppose I have following piece of code that contains an infinite loop:
function infiniteLoop() {
while(true) {
//do something, eg.
document.getElementById("someID").innerHTML = "Blah";
}
}
If we execute this code in an online compiler, browser will crash. I want to prevent that from happening. So I tried following code following this answer:
function willNotCrash() {
myVar = setInterval(infiniteLoop, 5000);
setTimeout(function(){
clearInterval(myVar);
}, 4000);
}
This code doesn't make the browser to crash, because I am stopping the execution before infiniteLoop() gets called by clearInterval(myVar).
My question is how do I stop executing such functions if they don't response within some period of time (eg. after 5 seconds or before the browser is crashed).
For example, if we copy paste following java code in https://www.compilejava.net/
public class HelloWorld {
public static void main(String[] args) {
while(true) {
System.out.println("Blah");
}
}
}
we get a nice output saying,
Script was taking longer than 5 seconds to execute so it was killed.
Here is my current code: http://js.do/code/106546
This is a bit tricky but perfectly doable. You need to tokenize the script and then rebuild it but insert a counter increment in every loop and function call. If the counter goes above some threshold, then bomb out. I did it here: https://littleminigames.com/
You can see the source at https://bitbucket.org/cskilbeck/littleminigames/src
The interesting bits are in wrapper.js (https://bitbucket.org/cskilbeck/littleminigames/src/ac29d0d0787abe93c75b88520050a6792c04d34d/public_html/static/js/wrapper.js?at=master&fileviewer=file-view-default)
Google escodegen, estraverse and esprima
I relied heavily on this: https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
wrapper.js, as requested:
// Don't obfuscate this file! We depend on the toString() of functions!
// this was all nicked from https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
(function(mainApp) {
'use strict';
var esprima = window.esprima,
estraverse = window.estraverse,
escodegen = window.escodegen,
errors = [],
eng,
Syntax = estraverse.Syntax;
// This implements the jankiest possible "source map", where we keep an array
// of [generatedLine, knownSourceLine]. Seems to essentially work.
function SourceNode(line, col, _sourceMap, generated) {
this.line = line;
this.col = col;
this.generated = generated;
}
SourceNode.prototype.toStringWithSourceMap = function toStringWithSourceMap() {
var code = [];
var mapLines = {};
var map = [];
// assumes that wrapCode adds two lines
var line = 3;
var lastMapLine = null;
function walk(node) {
if (typeof(node) === "string") {
if (node) {
code.push(node);
var matches = node.match(/\n/g);
if (matches !== null) {
line += matches.length;
}
}
} else if (node instanceof SourceNode) {
if (node.line !== null) {
if (!mapLines[line]) {
map.push([line, node.line]);
mapLines[line] = node.line;
}
}
walk(node.generated);
} else {
node.forEach(walk);
}
}
walk(this);
return {
code: code.join(''),
map: map
};
};
SourceNode.prototype.toString = function toString() {
return this.toStringWithSourceMap().code;
};
// This is used by escodegen
window.sourceMap = {
SourceNode: SourceNode
};
// TODO (chs): add in all the things that need to be masked
function runWrapper($userCode, __sys) {
var clear = __sys.clear,
setpixel = __sys.setpixel,
rectangle = __sys.rectangle,
box = __sys.box,
line = __sys.line,
getpixel = __sys.getpixel,
getpixeli = __sys.getpixeli,
keypress = __sys.keypress,
keyrelease = __sys.keyrelease,
keyheld = __sys.keyheld,
reset = __sys.reset;
__sys.userFunction = __sys.catchErrors($userCode);
}
function extractCode(fn) {
var code = fn.toString();
return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
}
function makeOneLine(code) {
return code.replace(/(\/\/[^\n]+|\n\s|\r\n\s*)/g, '');
}
var runTemplate = makeOneLine(extractCode(runWrapper));
function wrapCode(code, template, functionName, postCode) {
// avoid interpretation of the replacement string by using a fun.
// otherwise mo' $ mo problems.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter
return ("'use strict';" + template.replace(/\$userCode/, function() {
return 'function ' + functionName + '() {\n' + code + postCode + '\n}';
}));
}
var injectStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?');").body[0];
var injectElseStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?'); else ;").body[0];
function CallExpression(callee, args) {
this.callee = callee;
this.arguments = args;
}
CallExpression.prototype.type = Syntax.CallExpression;
function Identifier(name) {
this.name = name;
}
Identifier.prototype.type = Syntax.Identifier;
function BlockStatement(body) {
this.body = body;
}
BlockStatement.prototype.type = Syntax.BlockStatement;
function ReturnStatement(argument) {
this.argument = argument;
}
ReturnStatement.prototype.type = Syntax.ReturnStatement;
function FunctionExpression(id, params, body) {
this.id = id;
this.params = params;
this.body = body;
this.defaults = [];
this.expression = false;
this.generator = false;
this.rest = null;
}
FunctionExpression.prototype.type = Syntax.FunctionExpression;
function wrapId(node, defaultName) {
if (node.loc) {
var id = (node.id || {
name: null,
loc: null
});
var loc = id.loc || node.loc;
var name = id.name || defaultName;
return new Identifier(name + '$' + loc.start.line);
} else {
return node.id;
}
}
function instrumentAST(ast) {
var identifierStack = [];
function pushIdentifier(s) {
identifierStack[identifierStack.length - 1].push(s);
}
function popIdentifierStack() {
identifierStack.pop();
}
function pushIdentifierStack() {
identifierStack.push([]);
}
function peekLastIdentifier() {
var lastStackIdx = identifierStack.length - 1;
if (lastStackIdx >= 0) {
var stack = identifierStack[lastStackIdx];
if (stack.length) {
return stack[stack.length - 1];
}
}
return '';
}
pushIdentifierStack();
return estraverse.replace(ast, {
enter: function enterAST(node) {
switch (node.type) {
case Syntax.VariableDeclarator:
if (node.id.type === Syntax.Identifier) {
pushIdentifier(node.id.name);
}
break;
case Syntax.MemberExpression:
if (node.object.type === Syntax.Identifier) {
var id = node.object.name;
if (node.property.type === Syntax.Identifier) {
id += '__dot__' + node.property.name; // huh? why mangle these?
// console.log(id);
}
pushIdentifier(id);
} else if (node.property.type === Syntax.Identifier) {
pushIdentifier(node.property.name);
}
break;
case Syntax.FunctionDeclaration:
pushIdentifierStack();
break;
case Syntax.FunctionExpression:
pushIdentifierStack();
break;
default:
break;
}
return node;
},
leave: function leaveAST(node) {
switch (node.type) {
case Syntax.DoWhileStatement:
break;
case Syntax.ForStatement:
break;
case Syntax.FunctionDeclaration:
break;
case Syntax.FunctionExpression:
break;
case Syntax.WhileStatement:
break;
default:
return estraverse.SKIP;
}
// modify the BlockStatement in-place to inject the instruction counter
if(node.body.body === undefined) {
// they have used a non-block statement as the body of a function or loop construct
// not allowed for function declarations - should never get here
if(node.type === Syntax.FunctionDeclaration) {
errors.push({
message: "Missing {",
line: node.loc.start.line,
column: node.loc.start.column
});
}
else {
// otherwise insert the test
var newBody = angular.copy(injectElseStatement);
newBody.alternate = node.body;
node.body = newBody;
}
return estraverse.SKIP;
}
node.body.body.unshift(injectStatement);
if (node.type === Syntax.FunctionExpression) {
popIdentifierStack();
// __catchErrors(node)
node.id = wrapId(node, peekLastIdentifier());
return new CallExpression(
new Identifier("__sys.catchErrors"), [node]);
}
if (node.type === Syntax.FunctionDeclaration) {
popIdentifierStack();
// modify the BlockStatement in-place to be
// return __catchErrors(function id() { body });
var funBody = node.body;
node.body = new BlockStatement([
new ReturnStatement(
new CallExpression(
new CallExpression(
new Identifier("__sys.catchErrors"), [new FunctionExpression(
wrapId(node, peekLastIdentifier()), [],
funBody)]), []))
]);
}
return node;
}
});
}
// mainApp.sandbox('var a = 1; function update(frame) { clear(0); }').code
// give it the source code as a string
mainApp.sandbox = function(code) {
var rc = {};
this.errors = [];
try {
this.ast = instrumentAST(esprima.parse(code, { range: true, loc: true }));
this.map = escodegen.generate(this.ast, { sourceMap: true, sourceMapWithCode: true });
this.code = wrapCode(this.map.code, runTemplate, '', ';\n__sys.updateFunction = (typeof update === "function") ? update : null;');
}
catch(e) {
this.errors.push({
message: e.description,
line: e.lineNumber,
column: e.column
});
}
if(this.code) {
this.code = "eng.clientFunction = function(__sys) {" + this.code + "};";
}
};
mainApp.sandbox.prototype.searchMap = function(needle) {
// binary search
var lo = 0;
var hi = this.map.map.length;
var mid, here;
while (true) {
mid = lo + ((hi - lo) >> 1);
here = this.map.map[mid];
if (mid === lo || here[0] === needle) {
return here[1];
} else if (here[0] > needle) {
hi = mid;
} else {
lo = mid;
}
}
};
})(mainApp);
Typically all JavaScript runs in one thread, so it is impossible to run any JavaScript that could stop your loop while your loop is running. Using HTML5 web workers, you can run the infinite loop in a separate thread, and then you can terminate it:
var myWorker = new Worker( '/infinite.js ');
setTimeout( function ( ) {
myWorker.terminate( );
}, 5000 );
However your web worker won't have access to the DOM, so the contents of your infinite loop would need to be different that what you have in your question.
I found exactly what I was looking for in Bergi's comment,
Alternatively, place a if (Date.now() > dateAtStartOfExecution+5000) return; in every loop body.
So now my code looks like:
function infiniteLoop() {
dateAtStartOfExecution = Date.now();
while(true) {
//do something
document.getElementById("someID").innerHTML = "Blah";
if (Date.now() > dateAtStartOfExecution+5000) {
alert("Taking too much time. Killing.");
return;
}
}
}
If I run this code after 5 seconds I will get an alert and the execution will stop. Try this:
http://js.do/code/106565

Autocompleting XML values and attributes in Ace editor

I'm looking to autocomplete XML tags and attributes. The valid values will come from the server. For example,
If I have a tag such as,
<status></status>
and my cursor is inside the open and closing tags, I'd like to hit control + space and have only valid values appear in the drop-down. Such as: ok, error, warning, ...
Similarly for attributes,
<status audience="">ok</status>
If my cursor has focus inside the quotes I'd like only valid audiences to appear in the drop-down when hitting control + space.
Here's what I have so far. This completer completes words I'm typing. I just can't figure out how to know what kind of tag I'm inside and how to send specific values for that tag or attribute.
Any ideas or examples to point me to? Thanks, /w
function loadEditor() {
var langTools = ace.require("ace/ext/language_tools");
editor = ace.edit("editor");
editor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
});
editor.getSession().setMode("ace/mode/xml");
var myCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
callback(null, []);
return;
}
$.getJSON("completions.php?a=completions&prefix="
+ prefix + "&content=" + session, function(json) {
callback(null, json.map(function(c) {
console.log("value: " + c.value);
return {value: c.value, caption: c.caption, meta: c.meta, score:c.score};
}));
})
}
};
langTools.addCompleter(myCompleter);
}
So far I haven't been able to find any projects with XML completion, so this is what I have implemented.
The XhtmlTagInterpreter has one function getCompleteInfo() that returns a JavaScript object of the form {completeType: "attribute", tagName: "feline", attributeName: "breed"}. In this example it would try to auto-complete the breed attribute of <feline breed="" />.
It sends that data to the server for the appropriate breed values. This service is up to you to implement. e.g. https://www.example.com/services/mock/autocompleter/attribute.json?tagName=feline&attributeName=breed
The JSON returned will be something like this.
[
{"score":"1000","meta":"cats","caption":"siamese","value":"siamese"},
{"score":"1000","meta":"cats","caption":"burmese","value":"burmese"},
{"score":"1000","meta":"cats","caption":"bengal","value":"bengal"}
]
Here is the working JavaScript.
function XHtmlTagInterpreter(row, col, session) {
"use strict";
this.row = row;
this.col = col;
this.session = session;
this.leftOfCursor = null;
this.rightOfCursor = null;
this.leftType = null;
this.rightType = null;
}
/**
* Sets the left of cursor property used by other methods. This is a
* string without new lines from the beginning of the document to the
* letter just before the cursor.
*/
XHtmlTagInterpreter.prototype.setLeftOfCursor = function() {
"use strict";
this.leftOfCursor = "";
for (var r=0; r<=this.row; r++) {
if (r === this.row) {
var line = this.session.getLine(r);
for (var c=0; c<this.col; c++) {
this.leftOfCursor += line[c];
}
} else {
this.leftOfCursor += this.session.getLine(r);
}
}
};
/**
* Sets the right of cursor property used by other methods. This is a
* string without new lines from the letter just to the right of the cursor
* to the end of the document.
*/
XHtmlTagInterpreter.prototype.setRightOfCursor = function() {
"use strict";
this.rightOfCursor = "";
for (var r=this.row; r<=this.session.getLength(); r++) {
if (r === this.row) {
var line = this.session.getLine(r);
for (var c=this.col; c<line.length; c++) {
this.rightOfCursor += line[c];
}
} else {
this.rightOfCursor += this.session.getLine(r);
}
}
};
/**
* Sets the left type depending on first non-whitespace character to the
* left of the cursor position. We look for a right angle or a quotation.
* If a right angle we assume the cursor is inside a tag. If quotation the
* cursor is inside an attribute. We set the left type value to 'value'
* or 'attribute'.
*/
XHtmlTagInterpreter.prototype.setLeftType = function() {
"use strict";
this.setLeftOfCursor();
if (this.leftOfCursor === undefined || this.leftOfCursor.length === 0) {
this.leftType = "";
return;
}
for (var i=this.leftOfCursor.length-1; i>=0; i--) {
if (this.leftOfCursor[i] === " " || this.leftOfCursor[i] === "\t") {
continue;
}
if (this.leftOfCursor[i] === ">") {
this.leftType = "value";
return;
} else if (this.leftOfCursor[i] === '"') {
this.leftType = "attribute";
return;
} else {
this.leftType = "";
return;
}
}
};
/**
* Sets the right type depending on first non-whitespace character to the
* right of the cursor position. We look for a left angle or a quotation.
* If a left angle we assume the cursor is inside a tag. If quotation the
* cursor is inside an attribute. We set the right type value to 'value'
* or 'attribute'.
*/
XHtmlTagInterpreter.prototype.setRightType = function() {
"use strict";
this.setRightOfCursor();
if (this.rightOfCursor === undefined
|| this.rightOfCursor.length === 0) {
this.rightType = "";
return;
}
for (var i=0; i<this.rightOfCursor.length; i++) {
if (this.rightOfCursor[i] === " "
|| this.rightOfCursor[i] === "\t") {
continue;
}
if (this.rightOfCursor[i] === "<") {
this.rightType = "value";
return;
} else if (this.rightOfCursor[i] === '"') {
this.rightType = "attribute";
return;
} else {
this.rightType = "";
return;
}
}
};
/**
* Returns the tag name to be sent to autocompleter service.
* #returns {_L1.XHtmlTagInterpreter.prototype#pro;leftOfCursor#call;trim#call;replace|String}
*/
XHtmlTagInterpreter.prototype.getCompleteInfo = function() {
"use strict";
this.setLeftType();
this.setRightType();
if (this.leftType !== this.rightType) {
return "";
}
if (this.leftType === "value") {
var tagName = this.leftOfCursor.trim()
.replace(new RegExp("^.*<([a-z:]+).*?>$"), "$1");
return {completeType: "value", tagName: tagName};
} else if (this.leftType === "attribute") {
var tagName = this.leftOfCursor.trim()
.replace(new RegExp("^.*<([a-z:]+).*?([a-z:]+)\s*=\s*\"$"), "$1");
var attributeName = this.leftOfCursor.trim()
.replace(new RegExp("^.*<([a-z:]+).*?([a-z:]+)\s*=\s*\"$"), "$2");
return {completeType: "attribute", tagName: tagName,
attributeName: attributeName};
} else {
return null;
}
};
var loadEditor = function(editor) {
var chileCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
var line = session.getLine(pos.row);
if (undefined !== line) {
var interpreter = new XHtmlTagInterpreter(pos.row,
pos.column, session);
var completeInfo = interpreter.getCompleteInfo();
if (undefined === completeInfo || completeInfo === null
|| undefined === completeInfo.completeType
|| completeInfo.completeType === null
|| completeInfo.completeType.length === 0
|| undefined === completeInfo.tagName
|| completeInfo.tagName === null
|| completeInfo.tagName.length === 0) {
callback(null, []);
return;
}
$.getJSON(chileContextPath
+ "services/mock/autocompleter/"
+ encodeURIComponent(completeInfo.completeType)
+ ".json?tagName="
+ encodeURIComponent(completeInfo.tagName)
+ "&attributeName="
+ encodeURIComponent(completeInfo.attributeName),
function(json) {
callback(null, json.content.map(function(c) {
return {value: c.value, caption: c.caption,
meta: c.meta, score:c.score};
}));
})
}
} else {
callback(null, []);
return;
}
}
};
editor = ace.edit("chile-editor");
editor.setOptions({
enableBasicAutocompletion: [chileCompleter],
enableLiveAutocompletion: true,
enableSnippets: true,
});
editor.setTheme("ace/theme/clouds");
editor.getSession().setMode("ace/mode/xml");
editor.getSession().setUseWrapMode(true);
editor = loadXKbml(editor);
return editor;
};

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

Can this jQuery be done in vanilla JS?

I've got this working on mobile devices, but because of the 32kb gzip-ed of jQuery I wonder if it's possible to create this code
$(document).ready(function() {
$('body').addClass('js');
var $menu = $('#menu'),
$menulink = $('.menu-link'),
$wrap = $('#wrap');
$menulink.click(function() {
$menulink.toggleClass('active');
$wrap.toggleClass('active');
return false;
});
});
can be written in no library dependany vanilla JavaScript.
Can it be done? Where would I start?
JQuery uses javascript/DOMscripting to create its framework. Everything JQuery does, can be done in basic scripting. For example $('body').addClass('js') can be written as:
document.querySelector('body').className += ' js';
And $menulink.toggleClass('active'); as something like
var current = $menulink.className.split(/\s+/)
,toggleClass = 'active'
,exist = ~current.indexOf(toggleClass)
;
current.splice(exist ? current.indexOf(toggleClass) : 0,
exist ? 1 : 0,
exist ? null : toggleClass);
$menulink.className = current.join(' ').replace(/^\s+|\s+$/,'');
That's why JQuery wrapped this kind of code.
This jsfiddle contains a working example using javascript without a framework. Besides that it demonstrates how to program your own element wrapper.
Where to start? You'll have to dive into javascript I suppose. Or check this SO-question
For modern browsers only.®
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('js');
var wrap = document.getElementById('wrap');
var menuLinks = Array.prototype.slice.call(document.getElementsByClassName('menu-link'));
var toggleActive = function(element) {
element.classList.toggle('active');
};
menuLinks.forEach(function(menuLink) {
menuLink.addEventListener('click', function(e) {
menuLinks.forEach(toggleActive);
toggleActive(wrap);
}, false);
});
}, false);
var toggleClass = function (el, className) {
if(el) {
if(el.className.indexOf(className)) {
el.className = el.className.replace(className, '');
}
else {
el.className += ' ' + className;
}
}
};
document.addEventListener('DOMContentLoaded', function () {
document.body.className += ' js';
var $menu = document.querySelector('#menu'),
$menulink = document.querySelectorAll('.menu-link'),
$wrap = document.querySelector('#wrap');
$menulink.addEventListener('click', function (e) {
toggleClass($menulink, 'active');
toggleClass($wrap, 'active');
e.preventDefault();
});
});
There's always classList (workaround for incompatible browsers included).
Absolutely. Since jQuery is a subset of JavaScript (written entirely in JavaScript) any function you like can be duplicated. It's a matter of how much effort you want to put into it. Below is how I would duplicate the limited subset of jQuery in your post and it's reasonably cross-browser compatible (if a wee bit long...).
var Vanilla;
if (!Vanilla) {
Vanilla = {};
}
//execute this now to have access to it immediately.
(function () {
'use strict';
Vanilla.addHandler = function (elem, event, handler) {
if (elem.addEventListener) {
elem.addEventListener(event, handler, false);
} else if (elem.attachEvent) {
elem.attachEvent('on' + event, handler);
}
};
Vanilla.hasClass = function (elem, cssClass) {
var classExists = false;
//
if (elem && typeof elem.className === 'string' && (/\S+/g).test(cssClass)) {
classExists = elem.className.indexOf(cssClass) > -1;
}
//
return classExists;
};
Vanilla.addClass = function (elem, cssClass) {
if (elem && typeof elem.className === 'string' && (/\S+/g).test(cssClass)) {
//put spaces on either side of the new class to ensure boundaries are always available
elem.className += ' ' + cssClass + ' ';
}
};
Vanilla.removeClass = function (elem, cssClass) {
if (elem && typeof elem.className === 'string'&& (/\S+/g).test(cssClass)) {
//replace the string with regex
cssClass = new RegExp('\\b' + cssClass + '\\b', 'g');
elem.className = elem.className.replace(cssClass, '').replace(/^\s+/g, '').replace(/\s+$/g, ''); //trim className
}
};
Vanilla.toggleClass = function (elem, cssClass) {
if (Vanilla.hasClass(elem, cssClass)) {
Vanilla.removeClass(elem, cssClass);
} else {
Vanilla.addClass(elem, cssClass);
}
};
Vanilla.getElementsByClassName = function (cssClass) {
var nodeList = [],
classList = [],
allNodes = null,
i = 0,
j = 0;
if (document.getElementsByClassName1) {
//native method exists in browser.
nodeList = document.getElementsByClassName(cssClass);
} else {
//need a custom function
classList = cssClass.split(' ');
allNodes = document.getElementsByTagName('*');
for (i = 0; i < allNodes.length; i += 1) {
for (j = 0; j < classList.length; j += 1) {
if (Vanilla.hasClass(allNodes[i], classList[j])) {
nodeList.push(allNodes[i]);
}
}
}
}
return nodeList;
};
}());
//Now we have a proper window onload
Vanilla.addHandler(window, 'load', function () {
'use strict';
var body = document.body,
menu = document.getElementById('menu'),
menulink = [],
wrap = document.getElementById('wrap'),
i = 0,
menulinkClickHandler = function (e) {
var i = 0;
for (i = 0; i < menulink.length; i += 1) {
Vanilla.toggleClass(menulink[i], 'active');
}
Vanilla.toggleClass(wrap, 'active');
return false;
};
Vanilla.addClass(body, 'js');
menulink = Vanilla.getElementsByClassName('menu-link');
for (i = 0; i < menulink.length; i += 1) {
Vanilla.addHandler(menulink[i], 'click', menulinkClickHandler);
}
});

Categories

Resources