Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
I have a for-loop that cycles thru some html elements collected with jquery selectors and extracts some text or values from them. Each loop creates a new object. The object is simple, it is just text and a value. Console.log confirms that the object is created successfully each loop.
Outside the for-loop, I have a variable (kvObjs) that is initialized as an array. At the end of the for-loop, I push the new object into the array. But console.log confirms that the array stays empty.
This is part of a larger piece of code. This appears to be the part that isn't working. The specific function that isn't working is getKVs(), well, it works except for the part that tries to push the object on the array.
I promise you I looked thru all or almost all the "similar questions" and nothing clicked with me. I might have missed something in them, though. I feel like I'm overlooking something obvious.
I have tried to creating an array manually (var x = ["bob", "steve", "frank"]) and then setting another variable equal to that (var y = x) and that seems to work. I even created an array of objects, as in var x = [{"Key":"Bob","Value":10}, {"Key":"Steve","Value":5}], and I think that worked to. But my for-loop doesn't.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
.Jobs {
width: 300px;
margin: 0 auto;
}
.Jobs li {
display: grid;
grid-template-columns: 1fr 50px;
padding: 2px 5px 2px 7px;
align-items: center;
}
.Jobs .value {
text-align: right;
}
.Jobs p.value {
padding-right: 7px;
}
.even {
background-color: rgba(0,0,0,0.2);
}
</style>
<div class="Jobs">
<ul>
<li class="kvp">
<p class="key">Bob</p>
<input class="value" type="number" value="3"/>
</li>
<li class="kvp even">
<p class="key">Frank</p>
<input class="value" type="number" value="2"/>
</li>
<li class="kvp">
<p class="key">Tom</p>
<input class="value" type="number" value="8"/>
</li>
<li class="kvp total even">
<p class="key">Total</p>
<p class="value">13</p>
</li>
</ul>
</div>
<script>
class KV {
constructor(key, value) {
this.Key = key;
this.Value = value;
}
}
function getKVs(type) {
type = "." + type + " .kvp";
var elmts = $(type);
var kvObjs = [];
for (var i = 0; i < elmts.length; i++) {
var elmt = $(elmts[i]);
if(elmt.hasClass("total")) {
// do nothing
} else {
var k = elmt.find(".key").text();
var v = elmt.find("input").val();
var kv = new KV(k, v);
console.log(kv); // the kv object is successfully created
kvObjs.push[kv];
console.log(kvObjs.length); // but it is not being added to the array (length stays 0)
}
}
return kvObjs;
}
var x = getKVs("Jobs");
console.log(x); // so I'm transferring an empty array to x
</script>
I keep getting an empty array.
Problem in push() , replace kvObjs.push[kv] by kvObjs.push(kv);
read syntax of push() => array.push(element1, ..., elementN);
class KV {
constructor(key, value) {
this.Key = key;
this.Value = value;
}
}
function getKVs(type) {
type = "." + type + " .kvp";
var elmts = $(type);
var kvObjs = [];
for (var i = 0; i < elmts.length; i++) {
var elmt = $(elmts[i]);
if(elmt.hasClass("total")) {
// do nothing
} else {
var k = elmt.find(".key").text();
var v = elmt.find("input").val();
var kv = new KV(k, v);
console.log(kv); // the kv object is successfully created
kvObjs.push(kv);
console.log(kvObjs.length); // but it is not being added to the array (length stays 0)
}
}
return kvObjs;
}
var x = getKVs("Jobs");
console.log(x); // so I'm transferring an empty arr
.Jobs {
width: 300px;
margin: 0 auto;
}
.Jobs li {
display: grid;
grid-template-columns: 1fr 50px;
padding: 2px 5px 2px 7px;
align-items: center;
}
.Jobs .value {
text-align: right;
}
.Jobs p.value {
padding-right: 7px;
}
.even {
background-color: rgba(0,0,0,0.2);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="Jobs">
<ul>
<li class="kvp">
<p class="key">Bob</p>
<input class="value" type="number" value="3"/>
</li>
<li class="kvp even">
<p class="key">Frank</p>
<input class="value" type="number" value="2"/>
</li>
<li class="kvp">
<p class="key">Tom</p>
<input class="value" type="number" value="8"/>
</li>
<li class="kvp total even">
<p class="key">Total</p>
<p class="value">13</p>
</li>
</ul>
</div>
It looks like you have a small error in your code. See the line where you push the object into the array:
kvObjs.push[kv];
You need to use parentheses instead on the .push() method.
kvObjs.push(kv);
It seems that you have a syntax error:
use kvObjs.push(kv);
instead of
kvObjs.push[kv];
Related
I am making a javascript shopping cart. When you click on an item, the contents of that item are appended on the DOM via javascript. Having some difficulty with my code, specifically the sections where I bolded with '** **'
For my 'dropDown-amount' class, I have a number as the value but i cant seem to access that value later on in the code (see dropDown-price).
Also I have a For statement on my if/else statement that tries to loop through each index in 'items' array and to essentially count how many times one specific item is inside the list (so I can update quantity of x item on DOM) but having trouble getting everything to work. I know what I have to do to solve this problem and I know all 3 of these issues are linked together, just don't know what is exactly causing this to fail.
//dropdown menu hidden
const cartDropdown = document.querySelector('.cart-dropDown-items');
//every single + symbol
const addToCartButtons = document.querySelectorAll('.addToCart');
//price of item
const foodPrices = document.querySelectorAll('.selection-row-title');
//name of item
const foodNames = document.querySelectorAll('.selection-row-foodName');
//weight of item
const foodWeights = document.querySelectorAll('.selection-row-weight');
const items = [];
let total = 0;
for (let i = 0; i < addToCartButtons.length; i++) {
addToCartButtons[i].addEventListener('click', function() {
const newItem = document.createElement('div');
newItem.className = 'dropDown-item';
let amountItems = document.querySelector('.amount-items');
newItem.innerHTML =
`<div class='dropDown-title dropDown-info'>
${foodNames[i].innerHTML}
</div>
<div class='dropDown-amount dropDown-info'>
**<p class='amount-items'>${1}</p>**
</div>
<div class='dropDown-price dropDown-info'>
**${Number(foodPrices[i].innerHTML.substring(1)) * Number(amountItems.textContent)}**
</div>`;
console.log(newItem)
// if item currently exists in array, just update amount in checkout and increase count++
if (items.includes(addToCartButtons[i].value)) {
items.push(addToCartButtons[i].value); **
for (let i = 0; i < items.length; i++) {
if (items[i].includes(addToCartButtons[i].value)) {
Number(amountItems.innerHTML) + 1;
}
} **
}
// if items does not exist in array, update dom with new item UI and count = 1 by default
else {
items.push(addToCartButtons[i].value);
cartDropdown.appendChild(newItem);
}
console.log(items)
})
}
.cart-dropDown-items {}
.dropDown-title {}
.dropDown-item {
display: flex;
align-items: center;
text-align: center;
background-color: orange;
margin: 3px;
padding: 4px;
}
.dropDown-info {}
.dropDown-title {
width: 40%;
}
.dropDown-amount {
width: 30%;
text-align: center;
}
.dropDown-amount p {
border: 1px solid black;
width: 35%;
padding: 8px;
margin: 0 auto;
background-color: white;
}
.dropDown-price {
width: 30%;
}
<!--cart dropDown-->
<div class='cart-dropDown'>
<div class='cart-dropDown-header'>
<p>My Carts</p>
<p>Personal Cart</p>
<p class='cart-dropDown-close'>Close</p>
</div>
<div class='cart-dropDown-items'>
<!--
<div class='dropDown-item'>
<div class='dropDown-title dropDown-info'>Mixed bell pepper, 6 ct</div>
<div class='dropDown-amount dropDown-info'>1</div>
<div class='dropDown-price dropDown-info'>$9.84</div>
</div>
next unique item...
-->
</div>
<div class='cart-dropDown-checkout'>
<div class='cart-dropDown-checkout1'>
<p>Go to Checkout</p>
</div>
<div class='cart-dropDown-checkout2'>
<p>$0</p>
</div>
</div>
</div>
</div>
I have a simple function that is supposed to check if an element has a class. If it doesnt have the class, it should add the class. It should also iterate through all other elements in the series, and deselect them. So only 1 element should be shown at any time. I have looked at this, this, this and this as well as a bunch of others. I know the function should be simple, but I just cant resolve it.
const changeFilter = (itemNumber) => {
// grab element that has been clicked
let selector = document.getElementsByClassName(itemNumber);
// check if element doesnt has the class name that underlines it
if (!selector[0].classList.contains("darken-filter-selector")) {
// add the class if it doesnt have it
selector[0].classList.add("darken-filter-selector")
} else {
// iterate through all the elements, check if it has the class
// remove it if it doesnt have the class
for (let i = 0; i < 7 ; i++) {
if(i !== itemNumber) {
return selector[i].classList.contains("darken-filter-selector") ? selector[0].classList.remove("darken-filter-selector"):null
}
}
}
};
And it should not look like this (what is currently happening)
but rather should only allow for one selected element at a time...
The simplest way is to store the previous itemNumber into global variable like previousItemNumber.
Set the initial value of previousItemNumber to -1 (lower than 0) so that it can be used on changeFilter function.
And on changeFilter function, first, you check if previousItemNumber is existed or not (in other words, already selected item is existed or not when selecting new item) and if existed, remove the className there.
Add the className for the selected item and set the current itemNumber to previousItemNumber.
let previousItemNumber = -1;
const changeFilter = (itemNumber) => {
// grab element that has been clicked
let selector = document.getElementsByClassName(itemNumber);
if (previousItemNumber === itemNumber) {
return;
}
// Remove the class from previous selector
if (previousItemNumber >= 0) {
selector[previousItemNumber].classList.remove("darken-filter-selector");
}
selector[itemNumber].classList.add("darken-filter-selector");
previousItemNumber = itemNumber;
};
You can achieve the desired using input type="checkbox" and a bit of JS:
const EL_checkboxesOne = document.querySelectorAll(".checkboxesOne");
const checkboxesOne = (EL_group) => {
const inputs = EL_group.querySelectorAll('[type="checkbox"]');
EL_group.addEventListener("input", (ev) => {
[...inputs].filter(EL => EL !== ev.target).forEach(EL => EL.checked = false);
});
};
EL_checkboxesOne.forEach(checkboxesOne);
.checkboxesOne {
display: flex;
font: bold 16px/1.4 sans-serif;
color: #666;
}
.checkboxesOne input {
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
clip: rect(0 0 0 0);
clip-path: inset(100%);
}
.checkboxesOne span {
padding: 10px 0;
margin: 0 10px;
user-select: none;
cursor: pointer;
}
.checkboxesOne input:checked + span {
box-shadow: 0 4px green;
}
<div class="checkboxesOne">
<label><input type="checkbox" name="filter" value="all"><span>ALL</span></label>
<label><input type="checkbox" name="filter" value="new"><span>NEW</span></label>
<label><input type="checkbox" name="filter" value="wip"><span>WIP</span></label>
<label><input type="checkbox" name="filter" value="hot"><span>HOT</span></label>
<label><input type="checkbox" name="filter" value="won"><span>WON</span></label>
<label><input type="checkbox" name="filter" value="lost"><span>LOST</span></label>
<label><input type="checkbox" name="filter" value="dnc"><span>DNC</span></label>
</div>
I went with this function, as I thought it was the easiest and required the least modification from my original function
const changeFilter = (itemNumber) => {
// grab element that has been clicked
let selector = document.getElementsByClassName(itemNumber);
// check if element doesnt has the class name that underlines it
if (!selector[0].classList.contains("darken-filter-selector")) {
// add the class if it doesnt have it
selector[0].classList.add("darken-filter-selector");
}
// iterate through all the elements, check if it has the class
// remove it if it doesnt have the class
for (let i = 0; i < 7; i++) {
if (i !== itemNumber) {
let otherEls = document.getElementsByClassName(i);
if (otherEls[0].classList.contains("darken-filter-selector")) {
otherEls[0].classList.remove("darken-filter-selector");
}
}
}
};
This is my suggestion. It uses Event Delegation. Change the style as you like.
options.onclick = function(e) {
if (e.target.tagName != "LI") return;
var selected = options.querySelector(".active");
if (selected) {
selected.removeAttribute("class");
}
e.target.setAttribute("class", "active");
console.log(e.target.dataset.option);
};
#options {
display: flex;
justify-content: center;
}
#options li {
margin: 0 10px 0 10px;
padding: 10px;
cursor: pointer;
list-style: none;
}
#options li.active {
background-color: green;
pointer-events: none;
border-radius: 5px;
}
<ul id="options">
<li data-option="all">ALL</li>
<li data-option="new">NEW</li>
<li data-option="wip">WIP</li>
<li data-option="hot">HOT</li>
<li data-option="won">WON</li>
<li data-option="lost">LOST</li>
<li data-option="dnc">DNC</li>
</ul>
You can also do without using Data Attribute (data-option="..."): just use e.target.innerHTML.
I've read up on inserting css rules using Javascript and have managed to get it working (after some trial & error). So I have 2 questions:
Q.1 Why is an index < 1 not working - see Mozilla example (and many others) below:
// push a new rule onto the top of my stylesheet - doesn't work...
myStyle.insertRule("#blanc { color: white }", 0); // returns Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to insert the rule.
// change the index and it works!
myStyle.insertRule("#blanc { color: white }", 1);
This article by David Walsh (very helpful), explains that the default for index is -1. He uses 1 in his example, which is what worked for me. Anything less than 1, ie 0 or -1 (as per the default) threw the following errors:
Index -1 error:
Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': The index provided (4294967295) is larger than the maximum index (2071).
Index 0 error:
Uncaught DOMException: Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to insert the rule.
It's not a huge problem, but it does mean I can't control specificity. I can use !important or rework the css/inserted rule so that it overrides any existing styles, but as I'm just learning JavaScript, I'd really like to know why it's not working as expected. Does anyone have any ideas?
Q.2 Having got it to work using index 1, I now want to pull in values dynamically. I have the item names in an array, which is used to create multiple objects, inside which are the property values I want to use for the individual style rules for that object.
Basically what I'm trying to output is this (which works):
styleSheet.insertRule("#item { border-top-color: #000000; border-right-color: #ffffff; }", 1);
But using variables, something like this:
styleSheet.insertRule("[itemName] { border-top-color: [itemName.value1]; border-right-color: [itemName.value2]; }", 1); // itemName.valueX being the object's array item
I've tried heaps of things, but I can't get the array item bit to work, ie colour and colour4 should actually be itemName.value1/2 or a var that equals the same. This is the closest I've got so far...
styleSheet.insertRule("#" + name + " { border-top-color: " + colour + "; border-right-color: " + colour4 + " !important; }", 1); // 1st rule works, 2nd doesn't show anything
It all works lovely if I write it manually (as per the 1st example), but how to do it dynamically? I've found info on insertRule, but not using dynamic values - can anyone help/point me in the right direction?
Many thanks in advance!
Expanded WIP for more clarity:
function itemColours() {
for (i = 3; i < itemsArray.length; i++) {
let name = itemsArray[i];
let colour = #000000;
console.log(item1.value); // returns the hex value I want to use in the rule
styleSheet.insertRule("#" + name + " { border-top-color: " + colour + "; border-right-color: " + name + ".value !important; }", 1);
// rule 1 works, rule 2 doesn't...
}
Update 2
This demo:
can accept user data to use insertRule() and deleteRule().
has an Add Set button which will create and append a clone of the <form> part of the document
has 3 styleSheets Bootstrap [0], CSSOM, and the <style> tag.
Demo 4
// Just for demo
.as-console-wrapper {
width: 170px;
max-height: 40px;
transform: translateX(340px)
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' title='Bootstrap-3.3.7'>
<link href='https://glpjt.s3-us-west-1.amazonaws.com/ir/cssom.css' rel="stylesheet" title='CSSOM'>
<style title='Tag'>
body {
max-width: 96%;
visibility: hidden;
font: 400 16px/1.2 Verdana
}
ul.x-list.x-list {
margin-left: 0;
padding-left: 1em;
text-indent: -1em;
list-style-type: none;
}
li.x-item.x-item {
list-style: none;
line-height: 1.5;
}
.x-flex.x-flex {
display: flex;
justify-content: center;
align-items: center;
}
.x-col {
display: flex;
flex-direction: column
}
option::before {
content: attr(name)
}
#idx {
max-width: 6ch;
}
label {
margin: 10px auto 10px -15px
}
#add {
position: fixed;
z-index: 1;
top: 50px;
left: 20px
}
</style>
</head>
<body>
<main class='container' style='padding:50px 0 20px 20px'>
<form id='cssom' class='row x-col'>
<div class='btn-group col-sm-12'>
<input id='add' class='btn-lg btn-success' type='button' value='Add Set'>
</div>
<section class='row'>
<!--=================================[0]-->
<fieldset class='set0 col-sm-12'>
<hr>
<div class='row x-flex'>
<!--=================================[1]-->
<label class='form-control-label col-sm-2'>CSS Rule</label>
<textarea id='rul0' class='form-control col-sm-10' rows='2'>li.x-item.x-item::before {content: '\1f539\00a0';list-style:none;font-size:small;position:relative;bottom:2px}</textarea>
</div>
<div class='form-inline row'>
<label class='form-control-label col-sm-2'>Stylesheet</label>
<select id='sht0' class='form-control col-sm-4'>
<optgroup label="LINK">
<!--================================[3]-->
<option value='0' selected name='Bootstrap-3.3.7'> [0]</option>
<option value='1' name='CSSOM'> [1]</option>
</optgroup>
<optgroup label="STYLE">
<option value='2' name='Tag'> [2]</option>
</optgroup>
</select>
<label class='form-control-label col-sm-1'>Rule Index</label>
<input id='idx0' class='form-control col-sm-1' type='number' min='-1' value='0'>
<!--==========[4]-->
<div class="btn-group col-sm-4">
<!--=====[5]-->
<input id='ins0' class='btn btn-primary' type='button' value='Insert Rule' onclick='modRule(this)'>
<!--======[6]-->
<input id='del0' class='btn btn-danger' type='button' value='Delete Rule' onclick='modRule(this)'>
</div>
</div>
</fieldset>
</section>
<hr><br>
</form>
<hgroup class='x-inline'>
<!--====================================[hgroup.x-inline]-->
<h1 class='h1'>CSSStyleSheet</h1>
<h2 class='h2'>.insertRule()</h2>
</hgroup>
<article class='text-primary'>
<blockquote class='blockquote'>
<h3 id="Restrictions" class='h3'>Restrictions</h3>
<p>CSS stylesheet rule-lists have a number of intuitive and not-so-intuitive <a class="external" href="https://drafts.csswg.org/cssom/#insert-a-css-rule">restrictions</a> affecting how and where rules can be inserted. Violating these will likely
cause an error raised as a <a title="The DOMException interface represents an abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API." href="https://developer.mozilla.org/en-US/docs/Web/API/DOMException"><code>DOMException</code></a> </p>
<!--==========================[ul.x-list | li.x-fade]-->
<ul id='list' class='list-group-flush x-list'>
<li class='list-group-item-text fade x-fade x-item'>If index > number of rules in the stylesheet (the <a title="A CSSRuleList is an (indirect-modify only) array-like object containing an ordered collection of CSSRule objects." href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList"><code>CSSRuleList</code></a>.length),
then aborts with IndexSizeError.</li>
<li class='list-group-item-warning x-item'>If rule cannot be inserted at index 0 due to some CSS constraint, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-danger x-item'>If more than one rule is given in the rule parameter, then aborts with SyntaxError</li>
<li class='list-group-item-text x-fade x-item'>If trying to insert an #import at-rule after a style rule, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-text x-fade x-item'>If rule is #namespace at-rule and list has more than just #import at-rules and/or #namespace at-rules, then aborts with InvalidStateError.</li>
</ul>
</blockquote>
<footer class='blockquote-footer'>
<!--===============================[[cite.x-cite]-->
<cite class='x-cite'><a href='https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule#Restrictions'>CSSStyleSheet.insertRule() - Wed APIs #Restrictions | MDN</a></cite> </footer>
</article>
</main>
<script>
var cnt = 0;
var form = document.forms[0];
var add = document.getElementById('add');
function modRule(ID) {
var e = window.event;
var i = ID.id.split('').pop();
console.log('ruleIndex: ' + i);
var sheets = document.styleSheets;
var sheet = document.getElementById('sht' + i);
var rulez = document.getElementById('rul' + i);
var index = document.getElementById('idx' + i);
var vSht = parseInt(sheet.value, 10);
var vIdx = parseInt(index.value, 10);
var vRul = rulez.value;
if (e.target.value === 'Delete Rule') {
switch (vSht) {
case 0:
sheets[0].deleteRule(vIdx);
break;
case 1:
sheets[1].deleteRule(vIdx);
break;
case 2:
sheets[2].deleteRule(vIdx);
break;
default:
sheets[0].deleteRule(vIdx);
break;
}
} else if (e.target.value === 'Insert Rule') {
switch (vSht) {
case 0:
sheets[0].insertRule(vRul, vIdx);
break;
case 1:
sheets[1].insertRule(vRul, vIdx);
break;
case 2:
sheets[2].insertRule(vRul, vIdx);
break;
default:
sheets[0].insertRule(vRul, vIdx);
break;
}
} else {
return;
}
}
add.addEventListener('click', addSet, false);
function addSet(e) {
cnt++;
var set = document.querySelector('.set0');
var opt = document.options
var dupe = set.cloneNode(true);
dupe.className = 'set' + cnt;
var fields = Array.from(dupe.querySelectorAll('[id]'));
var ids = fields.map(function(ID, idx) {
var zero = ID.id.lastIndexOf("0");
ID.id = ID.id.slice(0, zero);
ID.id = ID.id + cnt;
if (ID.id === 'rul' + cnt) {
ID.textContent = 'p {color:red}';
}
console.log('id: ' + ID.id + ' val: ' + ID.value);
return ID.value;
});
form.appendChild(dupe);
}
</script>
</body>
</html>
Update
If the stylesheet is big, wouldn't it be more efficient to dynamically create a new stylesheet for them?
Yes, but not an external <link> it's far more efficient and a ton more easier to dynamically manipulate a <style> block at the bottom of the </head>.
Not only is it simple, it's powerful since it's at the bottom of the cascade it'll override anything from an external <link>.
Also another thing to consider is that a HTTP request isn't needed unlike an external file which requires it and that' adds to your website's latency.
Demo 3 features 3 functions:
injectCSS() will be called mere microseconds before it's counterpart injectJS() at window.onload. It will create a <style> block within the </head> along with whatever styles we want initially.
injectJS() loads after injectCSS() because as a general rule style should always load before script. It will create a <script> tag and append it as the last child of the <body> tag specifically right before the closing </body> tag. Just like injectCSS() it may have anything within its tags that's script.
inject() calls both injectCSS() and injectJS() asynchronously to ensure that the former will always load before the latter.
As far as this demo relates to the OP, injectCSS() is the function that we should concern ourselves with as was already explained previously.
Details are commented in the demo
For faster loading time, please review the PLUNKER instead.
Demo 3
<!DOCTYPE html>
<html>
<head>
<style>
html {
font: 400 100%/1 Consolas;
}
html,
body {
height: 100%;
width: 100%;
}
main {
height: auto;
width: 100%;
padding: 10px;
}
section {
height: auto;
width: 100%;
padding: 10px
}
fieldset {
min-width: 70%;
margin: 20px 0;
padding: 10px;
}
var {
background: rgba(0, 0, 0, .7);
color: lime;
}
</style>
</head>
<body>
<h1></h1>
<main>
<p><var>injectCSS =</var> All paragraphs will be red</p>
<section>
<form id='test0'>
<fieldset>
<legend>Test Log</legend>
<label for='msg0'>injectCSS()...:
<output id='msg0'></output></label>
<br>
<label for='msg1'>injectJS()....:
<output id='msg1'></output></label>
</fieldset>
</form>
</section>
<section>
</section>
</main>
<footer>
<p>Review this page with Firebug/DevTools and we'll see an extra <style> tag in the <head> and we'll see an extra <script> tag right before the closing <\body> tag.</p>
</footer>
<script>
// HTMLFormControlsCollection★
var x0 = document.forms[0].elements;
var m0 = x0.msg0;
var m1 = x0.msg1;
// Input strings of styles and scripts that are to be injected
var css = "p {color:red}";
var js = "document.querySelector('h1').innerHTML = '<var>injectJS =</var> H1 HTML'";
/* This function manages injectCSS() and injectJS() functions by using
|| the async/await★ keywords. Times are provided by
|| performance.now()★ method.
*/ //* ✎ Delete/add the first * to disable/enable this version of inject().
// The proceeding edit ✎ must be done as well.
var inject = async function() {
var wait0 = injectCSS.call(this, css);
var wait1 = injectJS.call(this, js);
m0.value = performance.now();
var init1 = await wait1;
m1.value = performance.now()
return false;
};
/*/// ✎ Delete/add the first / to enable/disable this version of inject().
// The previous edit ✎ must be done as well.
var inject = function() {
injectCSS.call(this, css);
m0.value = performance.now();
injectJS.call(this, js);
m1.value = performance.now()
return false;
};
/* These 2 functions do the same thing but with different content.
|| They could be refactored into one function but I made them
|| separately to show injectCSS() sepatately for QA SO46985099.
|| Both creates a tag, then writes the code in it, and then appends
|| it to DOM.
*/
function injectCSS(style) {
var sty = document.createElement("style");
sty.innerHTML = style;
document.querySelector('head').appendChild(sty);
}
function injectJS(script) {
var scr = document.createElement("script");
scr.innerHTML = script;
document.body.appendChild(scr);
}
/* The main function inject() is called at window.load. This is the last
|| loading event possible in which we are able call our function. This
|| ensures that specific CSS is loaded before specific JS is called.
|| This is the last step in the loading process, therefore there should be
|| no more styles to render or script that blocks.
*/
window.onload = inject;
</script>
</body>
</html>
Demo Outline
Collects all <link> and <style> into a styleSheetList using document.stylesheets
Converts it into an array called sheetArray with Array.from()
The target is the 3rd <link> which is sheetArray[2]
Then there are 2 rules inserted at index 0 and index 1 successfully.
This is done through a for loop and arrays as the dynamic parameters and interpolation of Template Literals.
This demo does not function on SO, go to PLUNKER for a functioning demo.
Note: In the demo content is an excerpt from MDN that defines the restrictions of insertRule(). The highlighted items may apply to your specific errors.
Demo 1 - index.html [Review PLUNKER for a working demo]
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css' rel='stylesheet'>
<link href="style.css" rel="stylesheet">
<link href='cssom.css' rel="stylesheet">
</head>
<body>
<main class='container'>
<hgroup class='x-inline'>
<!--====================================[hgroup.x-inline]-->
<h1 class='h1'>CSSStyleSheet</h1>
<h2 class='h2'>.insertRule()</h2>
</hgroup>
<article class='text-primary'>
<blockquote class='blockquote'>
<h3 id="Restrictions" class='h3'>Restrictions</h3>
<p>CSS stylesheet rule-lists have a number of intuitive and not-so-intuitive <a class="external" href="https://drafts.csswg.org/cssom/#insert-a-css-rule">restrictions</a> affecting how and where rules can be inserted. Violating these will likely
cause an error raised as a <a title="The DOMException interface represents an abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API." href="https://developer.mozilla.org/en-US/docs/Web/API/DOMException"><code>DOMException</code></a> </p>
<!--==========================[ul.x-list | li.x-fade]-->
<ul class='list-group-flush x-list'>
<li class='list-group-item-text x-fade'>If index > number of rules in the stylesheet (the <a title="A CSSRuleList is an (indirect-modify only) array-like object containing an ordered collection of CSSRule objects." href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList"><code>CSSRuleList</code></a>.length),
then aborts with IndexSizeError.</li>
<li class='list-group-item-warning'>If rule cannot be inserted at index 0 due to some CSS constraint, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-danger'>If more than one rule is given in the rule parameter, then aborts with SyntaxError</li>
<li class='list-group-item-text x-fade'>If trying to insert an #import at-rule after a style rule, then aborts with HierarchyRequestError.</li>
<li class='list-group-item-text x-fade'>If rule is #namespace at-rule and list has more than just #import at-rules and/or #namespace at-rules, then aborts with InvalidStateError.</li>
</ul>
</blockquote>
<footer class='blockquote-footer'>
<!--===============================[[cite.x-cite]-->
<cite class='x-cite'><a href='https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule#Restrictions'>CSSStyleSheet.insertRule() - Wed APIs #Restrictions | MDN</a></cite> </footer>
</article>
</main>
<script>
var sheets = document.styleSheets;
var sheetArray = Array.from(sheets);
var sel = ['ul.x-list', 'li::before'];
var dec = [`margin-left: 0; padding-left: 1em; text-indent: -1em;`, `content: '🔹';`];
var idx = [0, 1];
var qty = idx.length;
for (let r = 0; r < qty; r++) {
sheetArray[2].insertRule(`${sel[r]} {${dec[r]}}`, idx[r]);
}
</script>
</body>
</html>
Deno 2 - cssom.css
/*0*/
hgroup.x-inline {display:flex; justify-content:center; align-items: center;}
/*1*/
ul.x-list.x-list {list-style: none;}
/*2*/
li.x-fade.x-fade {color:rgba(0,0,0,.3);}
/*3*/
cite.x-cite.x-cite {position:relative; left:60%}
/*4*/
cite.x-cite.x-cite::before {content:"\2014 \00A0"}
/*5*/
.blockquote-footer::before {content: '';}
/*6*/
li.list-group-item {line-height:1.5}
/*7*/
a {text-shadow: 2px 5px 3px rgba(192,192,192,.8)}
I have been looking around, googling here and there, how to properly code a very simplistic calculator. More specifically a Boolean calculator with set values. If I've lost you, bear with me I'll try to explain.
I have the need to use checkboxes to set a input.value. This value will be picked up elsewhere in a Web-Java applet; hence the need for input.value.
To save time and confusion I have built a small snippet using JS Fiddle, and realised I have no real idea how to work with Numbers in JS. Everything I have learned so far has been self taught using the web and various other sources.
I struggle in understanding efficient ways of writing logic, but can scrape by, by writing really simplistic logic. The more I write and ask questons the more I learn. So I've come here seeking some advice on how to minimize my given code snippet.
It's not robust, It's not elegant; but it's my route in.
function calculate() {
// set the variables
var a = document.getElementById('checkboxopt');
var b = document.getElementById('checkboxopt1');
var c = document.getElementById('pnvar');
var d = document.getElementById('adjvar');
var e = document.getElementById('adjvar2');
var i = 0;
var i2 = 0;
if (a.checked) {
console.log('Arg True')
i = i + 80;
d.value = i;
} else {
console.log('Arg False')
d.value = i;
}
if (b.checked) {
console.log('Arg True')
i2 = i2 + 30;
e.value = i2
} else {
console.log('Arg False')
e.value = i2;
}
console.log(i, i2);
c.value = i + i2;
};
var cbs = document.querySelectorAll('[type="checkbox"]');
[].forEach.call(cbs, function(cb) {
cb.addEventListener("click", function() {
console.log(this.id);
calculate();
});
});
calculate();
.editoropt {
font-family: Calibri, sans-serif;
width: 160px;
background: #f8f8ff;
padding: .5em;
border: solid 1px #ddd;
}
#checkboxopt {
float: left;
margin-right: 1em;
margin-top: 4px;
}
#checkboxopt1 {
float: left;
margin-right: 1em;
margin-top: 4px;
}
.pnvar {
width: 95%;
}
input:-moz-read-only {
/* For Firefox */
background-color: transparent;
border: none;
border-width: 0px;
}
input:read-only {
background-color: transparent;
border: none;
border-width: 0px;
}
<div class="seq-box-form-field editoropt ">
<label for="opt1"><span style="padding-right: 10px; vertical-align: 1px;">Default 80mm </span>
<input type="checkbox" name="checkboxopt" id="checkboxopt" value="true" checked />
<input type="hidden" name="opt1" id="opt1" value="true" />
</label>
</div>
<div class="seq-box-form-field editoropt ">
<label for="opt1"><span style="padding-right: 10px; vertical-align: 1px;">Add 30mm </span>
<input type="checkbox" name="checkboxopt1" id="checkboxopt1" value="true" />
<input type="hidden" name="opt2" id="opt2" value="true" />
</label>
</div>
<div class="editoropt">
<input class="pnvar" id="pnvar" name="pnvar" placeholder="Null" onkeydown="if (event.keyCode == 13) { event.preventDefault(); return false; }" value="" class="required" type="text">
<input name="adjvar" class="pnvar" id="adjvar" readonly value="0">
<input name="adjvar" class="pnvar" id="adjvar2" readonly value="0">
</div>
</div>
My Question
What would be the best way to simplify my given code, and improve it's performance. More Specifically, is there a more simplistic way of working with numbers within JS? Or am I on the right track with my current snippet?
If you are an experienced JS developer how would you tackle the desired result above?
A little detailed guidance is what I'm hoping for.
First, try to make code as reusable as possible. So,
if (a.checked) {
console.log('Arg True')
i = i + 80;
d.value = i;
} else {
console.log('Arg False')
d.value = i;
}
if (b.checked) {
console.log('Arg True')
i2 = i2 + 30;
e.value = i2
} else {
console.log('Arg False')
e.value = i2;
}
can be replaced as
function dummy(el, val) {
i2 += el.checked ? val : 0;
}
Second, do not have inline functions in HTML. It will dirty your HTML and make debug difficult. You should wrap all listeners in a wrapper function. This will enable you to even export all of them to separate file and you will know where to look.
Third, instead of hard coding value 80 or 30 in your code, make a map or bind it to element using data-attribute
Fourth and more Important one, use better variable names. a-e or i, i1, i2 are bad names. Only you will understand your code (till you are in context). Always use precise meaningful name. This will help in long run. I have even kept function names as long as 30+ chars just to define its purpose.
Also try to break your code in smaller functions. This will increase scope for reusing them.
You can refer updated code and ask any queries, if you have.
Hope it helps!
I have an HTML document. There are 2 fields:
First one is an hidden input field (with a pre selected value)
The other is an empty text input field. It displays the current value of the first one with jQuery.
Between that 2 fields, I have 9 buttons with different texts inside them.
With jQuery, when I click on a button it add his text in the visible field (separated by a coma) and it adds to himself an enabled class. This works.
When I click on the same button, the class is removed (this works).
What is not working:
I would like at the same time it removes his name from the field (with a coma) I With jQuery / JavaScript I have tried to convert with split() each name in the field (coma separated) to an array, to easily delete one of them when needed with splice().
What I haven done yet:
I need to convert-it back to a coma separated string, replacing the existing string value in the field ( I haven done this yet). I am not far.
My code on JS Fiddle
How can I achieve this?
My code in here:
jQuery(document).ready(function($){
if ($('.subscribe-text').val() == ''){
$('.subscribe-text').val($('.subscribe-text-get').val()+',')
}
$('.subscribe-button').click(function(){
if(!$(this).hasClass('enable'))
{
$('.subscribe-text').val($('.subscribe-text').val()+$(this).html()+',');
$(this).addClass('enable');
}
else if($(this).hasClass('enable'))
//
// I would like to remove the name in the field
// when a button is clicked and has 'enable' class
//
{
var myArray = new Array();
var myString = $('.subscribe-text').val();
myArray = myString.split(",");
var myItem = $(this).html()+' ';
var indexPos = myArray.indexOf(myItem);
if(indexPos != -1)
{
myArray = myArray.splice(indexPos, 1);
}
$('.subscribe-text').val(myArray);
$(this).removeClass('enable');
}
});
});
.subscribe-button {
background-color:#ddd
}
#buttons_x3 {
width:500px;
}
.subscribe-text {
width:100%;
font-size:9px;
}
.subscribe-button {
display: inline-block;
text-align:center;
padding:3px;
margin-bottom: 10px;
color: black;
height:20px;
width:100px;
margin:10px 20px 10px;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="my_custom_checkout_field">
<input type="hidden" class="subscribe-text-get" name="my_field_name" value="woo-multi-1">
</div>
<br>
<div id="buttons_x3">
<a class="subscribe-button">woo-single-1</a>
<a class="subscribe-button">woo-single-2</a>
<a class="subscribe-button">woo-single-3</a>
<a class="subscribe-button">woo-multi-1</a>
<a class="subscribe-button">woo-multi-2</a>
<a class="subscribe-button">woo-top-1</a>
<a class="subscribe-button">woo-top-2</a>
<a class="subscribe-button">woo-top-3</a>
<a class="subscribe-button">woo-special</a>
</div>
<br>
<div id="my_custom_checkout_field">
<input type="text" class="subscribe-text" name="my_field_name" value>
Be careful of a few things here:
1) Every time you click a button you are creating a new array with one long string inside.
2) Splice returns the value of the removed item, so each time a button is clicked you are setting the array string to be the item that you just removed.
Instead, initialize the array outside of your click function, and add each new item to the end. Set the value of the input with the toString() method. This will preserve the array of remaining values and their order.
jQuery(document).ready(function($){
var myArray = [];
if ($('.subscribe-text').val() == ''){
myArray[0] = $('.subscribe-text-get').val();
$('.subscribe-text').val( myArray.toString() );
}
var $item, length;
$('.subscribe-button').click(function(){
length = myArray.length;
if(!$(this).hasClass('enable')) {
$item = $(this).html();
myArray[length] = $item;
$('.subscribe-text').val( myArray.toString() );
$(this).addClass('enable');
}
else if($(this).hasClass('enable')) {
var myItem = $(this).html();
var indexPos = myArray.indexOf(myItem);
if(indexPos != -1) {
myArray.splice(indexPos, 1);
}
$('.subscribe-text').val( myArray.toString() );
$(this).removeClass('enable');
}
});
});
.subscribe-button {
background-color:#ddd
}
#buttons_x3 {
width:500px;
}
.subscribe-text {
width:100%;
font-size:9px;
}
.subscribe-button {
display: inline-block;
text-align:center;
padding:3px;
margin-bottom: 10px;
color: black;
height:20px;
width:100px;
margin:10px 20px 10px;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="my_custom_checkout_field">
<input type="hidden" class="subscribe-text-get" name="my_field_name" value="woo-multi-1">
</div>
<br>
<div id="buttons_x3">
<a class="subscribe-button">woo-single-1</a>
<a class="subscribe-button">woo-single-2</a>
<a class="subscribe-button">woo-single-3</a>
<a class="subscribe-button enable">woo-multi-1</a>
<a class="subscribe-button">woo-multi-2</a>
<a class="subscribe-button">woo-top-1</a>
<a class="subscribe-button">woo-top-2</a>
<a class="subscribe-button">woo-top-3</a>
<a class="subscribe-button">woo-special</a>
</div>
<br>
<div id="my_custom_checkout_field">
<input type="text" class="subscribe-text" name="my_field_name" value>
Here is my code on a jsFiddle