Using checkboxes to update UI in realtime - javascript

I'm currently in the process of trying to develop a smarter UI for one of my clients. However the only code I can use to develop this 'feature', is pure JS. I have no access to the source HTML or CSS files the only access I have is the ability to inject JavaScript through an external .js file.
I'm not too familiar with JS, but I can work my way around a basic script or two.
Scenario
What we're doing is allowing users to edit PDF Templates online using a software called Core Create. The UI accessed through the browser is quite cluttered and I would like to provide an option to hide and show UI elements <textareas>/<inputs> through the use of checkboxes.
Here is a very basic JS Fiddle that I have built with the
intention of hiding and displaying UI.
The page in question
Above is a screen grab of the page I am working with, on the left you can see the UI and its composition on the right within the 'Inspect Element' tool.
I have come to the conclusion that I need to iterate through the highlighted selection and link them accordingly with seven checkboxes. The result would then be a selection of checkboxes that would hide / display the correct UI element.
The Caveat
In realizing I cannot edit or introduce new HTML I noticed the lack of on-click attributes. So I'm a bit lost on how to invoke the JavaScript I will eventually build.
My Question
With my limited knowledge of JS I don't know how I would iterate though div elements editoraccvar - editoraccvar6 picking out the ones I need to manipulate.
Due to the lack of ID's / Names (I assume it would have to be done using Parent/Child rules somehow, as the classes are widley used by the rest of the UI. I would appreciate a small example demonstrating how I could achieve this, so I can learn from it.
I should clarify, I have already added the checkboxes to the page, I just need to build the JS link between the Checkbox and the UI element I'm attempting to target. You can find all attributes linking to these checkboxes included in the JS Fiddle.
EDIT // A Working Simplified Example;
Due to some confusion I have 'frankensteined' some code together to show the final result I am after. A working example of sorts. The actual result needs to target 7 Checkboxes and 7 Divisions. I'll list thier common properties below.
// This script is already in place and constructed by the system.
// Written inside script tags and located straight after 'editopt1'.
// $(document).ready(function() {
// $('#checkboxopt1').click(function() {
// if ($('#checkboxopt1').val() == 'true') {
// $('#opt1').val('false');
// $('#checkboxopt1').val('false');
// $('#checkboxopt1').prop('checked', false);
// $('#previewrefresh').trigger('click');
// } else {
// $('#opt1').val('true');
// $('#checkboxopt1').val('true');
// $('#checkboxopt1').prop('checked', true);
// $('#previewrefresh').trigger('click');
// };
// });
// });
function exFunction() {
// Check the function is called
console.log("200 : OK");
// grab all elements with the class, .field-summernote
var uiblocks = document.querySelectorAll('.field-summernote');
for (var i = 0; i < uiblocks.length; i++) {
var current = uiblocks[i];
if (current.className.indexOf('editoraccvar') < 0) //not found: -1
return;
// check elements in the array
console.log(current);
// control the elemets in the array.
if (document.getElementById('checkboxopt1').checked) {
uiblocks[0].style.display = 'block'; // display the element
} else {
uiblocks[0].style.display = 'none'; // hide the element
}
}
};
// Trigger the collection the check, and the control.
var x = document.getElementById("checkboxopt1");
x.addEventListener("click", function() {
console.log("Opt");
exFunction();
});
.editoraccvar1 {
width: 300px;
background: #0ff;
padding: .5em;
}
.editoropt1 {
width: 300px;
background: #ff0;
padding: .5em;
}
textarea {
display: block;
width: 95%;
resize: none;
padding: .5em;
}
<!-- I'm trying to hide & show this entire division... -->
<div class="seq-box-form-field field-summernote editoraccvar1 ">
<label for="accvar1">Ground Floor Info</label>
<div class="clearfix"></div>
<textarea id="richaccvar1" name="richaccvar1" class="summernote"></textarea>
<input type="hidden" name="accvar1" id="accvar1" value="" />
</div>
<!-- Using only what the system has supplied. -->
<div class="seq-box-form-field editoropt1 ">
<label for="opt1"><span style="padding-right: 10px; vertical-align: 1px;">Ground Floor </span>
<input type="checkbox" name="checkboxopt1" id="checkboxopt1" value="true" checked="true" />
<input type="hidden" name="opt1" id="opt1" value="true" />
</label>
</div>
Divisions <div class=""></div>
* editoraccvar,
editoraccvar1,
editoraccvar2,
editoraccvar3,
editoraccvar4,
editoraccvar5,
editoraccvar6*
Checkboxes <input id=""></input>
* checkboxopt,
checkboxopt1,
checkboxopt2,
checkboxopt3,
checkboxopt4,
checkboxopt5,
checkboxopt6,*

As far as I can see, your problem boils down to link checkboxes (that seem to have been generated in some way) to "division" parts of your html that you want to hide. Plus, you have to inject javascript code in the page (so I guess the less code the better).
One approach could be as follows:
// Wrap the code in an anonymus function, to avoid clustering the global space.
(function (domElements) {
// This is the callback that will fire when a checkbox is clicked.
function clickCallback() {
// the context of this callback is the DOM element thus we can access its attributes through this.
// extract the checkNumber of the class of the element. This number is the link to the division that we want to hide/show.
var checkNumber = ((/ editoropt(\d*) /).exec(this.className))[1],
checkBox = document.getElementById('checkboxopt' + checkNumber),
division = document.querySelectorAll('.editoraccvar' + checkNumber)[0];
// Hide/show division, update checkBox state.
toggleElements(division, checkBox, window.getComputedStyle(division).display === 'none');
}
function toggleElements(division, checkBox, isShown) {
// Toggle the division (show/hide) accordingly.
division.style.display = isShown ? 'block' : 'none';
// Due to the fact that the event listener is attached to the parent of the checkBox, we need to maintain consistency manually.
checkBox.checked = isShown;
}
// Remove from the array of DOMElements those that aren't checkboxes and add a click event listener to each of them.
domElements
.filter(function (el) {
return el.className.indexOf('editoropt') !== -1;
})
.forEach(function (el) {
el.addEventListener('click', clickCallback, false);
});
// Call the function passing the dom elements with class '.seq-box-form-field' as argument. Checkboxes are contained within them. Also, transform the nodelist
// into a proper array so that methods defined in Array.prototype can be used.
})([].slice.call(document.querySelectorAll('.seq-box-form-field')));
The code is commented and (I think) quite self-explanatory. However, if you have any doubt or want me to elaborate any point further, please, let me know.
Finally, here's the working fiddle.
UPDATE
Same function (more or less) but now it accepts an array of values that will correspond to the initial state of the checkboxes:
(function (domElements, cbState) {
function clickCallback() {
toggleElements(this.className);
}
function toggleElements(className, initialShow) {
var checkNumber = ((/ editoropt(\d*) /).exec(className))[1],
checkBox = document.getElementById('checkboxopt' + checkNumber),
division = document.querySelectorAll('.editoraccvar' + checkNumber)[0],
isShown = initialShow === undefined ? window.getComputedStyle(division).display === 'none' : initialShow;
division.style.display = isShown ? 'block' : 'none';
checkBox.checked = isShown;
}
domElements
.filter(function (el) {
return el.className.indexOf('editoropt') !== -1;
})
.forEach(function (el, index) {
el.addEventListener('click', clickCallback, false);
toggleElements(el.className, cbState[index]);
});
// Initial state of the checkboxes goes in the second parameter. The index in the array correspond to the checkbox position in the page.
})([].slice.call(document.querySelectorAll('.seq-box-form-field')), [false, false]);
Here's the Fiddle to play with. Hope it helps.

The other half of your problem, not addressed in the other answer has to do with events. Generally, adding an "onclick" attribute to the actual HTML is considered bad practice. You can attach event handlers with Javascript.
var a = document.getElementById("checkboxopt1");
a.addEventListener("click", exFunction, false);
See the manual for more info about how to use this.

Looks like that you need the elements that have the class "field-summernote", but not the class "editorbdyvar".
You can use a query selector to get elements by class name using the default tools from Javascript:
var items = document.querySelectorAll('.field-summernote');
for(var i = 0; i<items.length; i++){
var current = items[i];
if( current.className.indexOf('editoraccvar') < 0) //not found: -1
return;
//now you can manipulate the current element
console.log(current);
}

well ... you should either learn javascript, DOM, HTML and CSS or hire an somebody that can do it.
in my opinion the latter would come cheaper.
if not,
here goes something to put in your script.js file.
the checkboxes must have the id="toggleTextareas" respectively id="toggleInputs".
(function isolateScope() {
tryInit();
function tryInit() {
if(document.readyState!="complete"){
setTimeout(tryInit, 100);
}else{
createUI();
init();
}
}
function createUI(){
var div=document.createElement("div");
div.className="addon-floating-toolbar"
div.style.position="fixed";
div.style.zIndex="999999";
div.style.background="#EEE";
div.style.padding="5px";
div.innerHTML='<input type="checkbox" id="toggleTextareas">toggle Textareas<br>'
+'<input type="checkbox" id="toggleInputs">toggle Inputs';
document.body.appendChild(div);
}
function init() {
var tta=document.getElementById("toggleTextareas");
var ti=document.getElementById("toggleInputs");
var textareaVisible=true;
var inputVisible=true;
tta.onclick=toggleTextareas;
ti.onclick=toggleInputs;
function toggleTextareas() {
var elms=document.querySelectorAll("textarea");
textareaVisible=!textareaVisible;
if (textareaVisible) {
show(elms);
}else{
hide(elms);
}
}
function toggleInputs() {
var elms=document.querySelectorAll("input");
inputVisible=!inputVisible;
if (inputVisible) {
show(elms);
}else{
hide(elms);
}
}
function show(collection) {
for (var i = 0; i < collection.length; i++) {
collection[i].style.display="";
}
}
function hide(collection) {
for (var i = 0; i < collection.length; i++) {
collection[i].style.display="none";
}
}
}
})();
let me know if it works,
cheers.

You can traverse all your fields and generate a checkbox that will toggle it open/close for each of your fields. Also set the checkbox label as innerText of the corresponding field.
// Block to be run
generateCheckboxes = function() {
var button = document.getElementById("generateButton");
button.parentNode.removeChild(button);
// grab all elements with the class, .field-summernote
var uiblocks = [].slice.call(document.querySelectorAll('.field-summernote')).filter(function(x) {
return x.className.indexOf('editoraccvar') >= 0
});
if (!uiblocks.length) return;
var chcontainer = document.createElement('div');
chcontainer.style.display = "inline-block";
document.body.insertBefore(chcontainer, document.body.children[0]);
uiblocks.forEach(function(x) {
var cdiv = document.createElement('div');
var clabel = document.createElement('label');
clabel.innerHTML = x.innerText.trim();
var cinput = document.createElement('input');
cinput.type = 'checkbox';
cinput.checked = true;
cinput.onchange = function(ev) {
var checked = this.checked;
x.style.display = checked ? "" : "none";
}
cdiv.appendChild(clabel);
cdiv.appendChild(cinput);
cdiv.appendChild(document.createElement('br'));
chcontainer.appendChild(cdiv);
})
};
#container {
width: 150px;
}
input {
float: left;
}
label {
width: 120px;
display: block;
float: right;
text-align: left;
}
<button onclick="generateCheckboxes()" id="generateButton">Generate Checkboxes</button>
<div id="example" class="field-summernote editoraccvar">
<br/>
<br/>
<span>Zero</span>
<br/>
<textarea></textarea>
</div>
<div id="example1" class="field-summernote editoraccvar1">
<br/>
<br/>
<span>One</span>
<br/>
<textarea></textarea>
</div>
<div id="example2" class="field-summernote">
<br/>
<br/>
<span>Two</span>
<br/>
<textarea></textarea>
</div>
Fiddle

Related

How to create a search that filters by class name

I currently have a website that has multiple items. They are all individual div items. They all have one class name in common with several other class names as tags to help separate them (some tags are common among multiple div items)
I already have buttons set up that use data-filter=".exampleclass" and data-filter=".exampleclass2" etc. which work perfectly for sorting based on the class names. I am now trying to make a search bar where a user could type in the class name so I don't have to make buttons for them all.
document.getElementById("boxsearch").oninput = function() {
var matcher = new RegExp(document.getElementById("boxsearch").value, "gi");
for (var i = 0; i < document.getElementsByClassName("portfolio-item").length; i++) {
if (matcher.test(document.getElementsByClassName("category")[i])) {
document.getElementsByClassName("portfolio-item")[i].style.display = "inline-block";
} else {
document.getElementsByClassName("portfolio-item")[i].style.display = "none";
}
}
}
http://jsfiddle.net/kc2ot8ua/
I dont have the jquery file included so the buttons dont work (they work on my end) I just dont know how to use the search bar to search the class names.
This is the closest I could find to what I am trying to achieve: http://jsfiddle.net/mttgj1tt/5/
Filtering elements based on regular expression matches with one of their class names is an inefficient way to filter elements. Typically you'd build an index and use that with a more optimised search algorithm.
You might use one class to select the target set of elements, then loop over them and get their classList, then loop over those looking for matches, there's an example below. But this will also test other class names that have nothing to do with filtering or sorting (e.g. in the example below, "box" is used for display only, but elements are filtered by it anyway).
A better idea might be to add the filter and sorting values as a data- attribute, then they can be isolated from other side effects. I'd also suggest building an index of subject elements so you can find the ones you want first, then hide them.
Multiple getElementByClassName calls are expensive and unnecessary (particularly in a for loop). The example does one call per keyup.
function filterOnClass(baseClass, s) {
let re = new RegExp(s.trim(), 'i');
document.querySelectorAll('.' + baseClass).forEach(node => {
let cNames = Array.from(node.classList);
// Show all if search string is blank
if (s.trim() == '') {
node.classList.remove('hide');
// Otherwise, filter
} else if (cNames.some(cName => re.test(cName))) {
node.classList.add('hide');
} else {
node.classList.remove('hide');
}
});
}
.box {
height: 50px;
width: 100px;
border: 1px solid blue;
}
.hide {
display: none;
}
<input id="searchInput" onkeyup="filterOnClass('box', this.value)"><br>
<div class="box foo">foo</div>
<div class="box foo bar">foo bar</div>
<div class="box fum bar">fum bar</div>
<div class="box fum">fum</div>

How to specify with multiple objects in JS?

So I'm trying to make a code that allows me to change the image once I hover over it.
The initial code works. But I have 72 other images to go through with this feature. I'm trying to call each one individually so I don't have to repeat so much code.
I want a simple html code like
<img id="seal" src="img/seal/dantalion.png" onmouseover="rollover(dantalion)"
onmouseout="rollaway(dantalion)" />
<img id="seal" src="img/seal/vassago.png" onmouseover="rollover(vassago)"
onmouseout="rollaway(vassago)" />
Here is the code that works.
function rollover(img) {img.src = "img/seal/hover/vassago.png";}
function rollaway(img) {img.src = "img/seal/vassago.png";}
Here is what I want to do. Keep in mind please, I'm new to this sort of thing.
function rollover() {
dantalion.src = "img/seal/hover/dantalion.png";
vassago.src = "img/seal/hover/vassago.png";
}
function rollaway() {
dantalion.src = "img/seal/dantalion.png";
vassago.src = "img/seal/vassago.png";
}
How do I individually call the object in the HTML code?
This is a simple solution to your problem. On mouseover of the wrapper div #images we check if you are hovering an image and if so, update the image src with the hover url.
On mouseout or if you hover on a different image the images are reset back to the original src
var images = document.getElementById('images');
var prevEl;
function resetImages() {
if (prevEl) {
prevEl.src = prevEl.src.replace('/hover', '');
prevEl = null;
}
}
images.addEventListener('mouseover', function(e) {
resetImages();
if (e.target && e.target.nodeName == "IMG") {
prevEl = e.target;
e.target.src = e.target.src.replace('/seal', '/seal/hover');
}
});
images.addEventListener('mouseout', resetImages);
<div id="images">
<img src="img/seal/dantalion.png">
<img src="img/seal/vassago.png">
</div>
Here's an example using event delegation. I've tried to use only core JS APIs because you didn't mention any libraries but if you were using a library there's a good chance it would be able to do some of this for you.
document.body.addEventListener('mouseover', function(ev) {
var target = ev.target,
cls = target.classList;
if (!cls.contains('seal')) {
return;
}
cls.add('seal-over');
target.innerHTML = target.innerHTML.replace('/seal/', '/seal/hover/');
});
document.body.addEventListener('mouseout', function(ev) {
var target = ev.target,
cls = target.classList;
if (!cls.contains('seal')) {
return;
}
cls.remove('seal-over');
target.innerHTML = target.innerHTML.replace('/hover', '');
});
.seal {
border: 1px dotted #777;
height: 70px;
margin: 10px;
width: 200px;
}
.seal-over {
background: #eee;
}
<div class="seal">img/seal/dantalion.png</div>
<div class="seal">img/seal/vassago.png</div>
While my example changes the innerHTML you would change the src instead - I didn't have your images so I couldn't easily use img tags. The id attribute has to be unique so I've changed it to using a class instead. Adding the seal-over class isn't required, I just thought it made the example more interesting: you'd probably use a CSS :hover pseudo-class for that if this were real code.
One of many guides to delegation, you can find others with a quick search online:
https://davidwalsh.name/event-delegate
The idea is to add a single listener on an element higher up the tree. Events propagate up so you can react to events on descendent elements. The event target refers to the element on which the actual event occurred. The code checks whether this element is one of the elements we care about and then makes the changes accordingly.

collapse / expand of panel-heading body

The given below code used to collapse/expand. I used this code, where there were multiple panel-headings to collapse/expand. And it worked fine, but I don't know how this is working. I have taken this code from internet.
It would be helpful if someone can explain , whats going on here or point me to somehere, where I can read about this.
second, can I use this same function without changing on a page, where ther is only one panel-heading div.Could you give an example, if yes.
function bindCollapseEvents(panels, numPanels) {
for (var i = 0 ; i < panels.length ; i++) {
var heading = $(panels[i]).children('.panel-heading');
var bodyCollapse = $(panels[i]).children('.panel-body');
if (heading.length != 0 && bodyCollapse.length != 0) {
numPanels++;
// $(heading[0]).attr('data-toggle', 'collapse');
// Use this instead of the data-toggle attribute to let [more/less] be clicked without collapsing panel
if ($(heading[0]).attr('class') == 'panel-heading') {
$(heading[0]).click(toggleSingleCollapse);
}
$(heading[0]).attr('data-target', '#panelBodyCollapse');
$(heading[0]).attr('id', 'panelHeading');
$(heading[0]).css('cursor', 'pointer');
$(bodyCollapse[0]).attr('id', 'panelBodyCollapse');
}
}
return numPanels;
}
function toggleSingleCollapse(e) {
if (!$(e.target).is('a') && !$(e.target).is('input')) {
var glyphIcon = $(this).find('.glyphicon');
var className = $(glyphIcon[0]).attr('class');
if (className.indexOf('glyphicon-chevron-up') != -1) {
hideSingleCollapse($(e.currentTarget).attr('data-target'));
} else {
showSingleCollapse($(e.currentTarget).attr('data-target'));
}
}
}
function showSingleCollapse(e) {
var heading = $(e).parent().children('.panel-heading');
var glyphIcon = $(heading[0]).find('.glyphicon');
$(glyphIcon[0]).removeClass('glyphicon-chevron-down');
$(glyphIcon[0]).addClass('glyphicon-chevron-up');
$(e).collapse('show');
$(heading).find('a.btn').show();
}
function hideSingleCollapse(e) {
var heading = $(e).parent().children('.panel-heading');
var glyphIcon = $(heading[0]).find('.glyphicon');
$(glyphIcon[0]).removeClass('glyphicon-chevron-up');
$(glyphIcon[0]).addClass('glyphicon-chevron-down');
$(e).collapse('hide');
$(heading).find('a.btn').hide();
}
Main thing in the above code is switch logic, which is used to hide/show the respective content.
The only thing switch logic is doing here to add/remove respective CSS class on the elements which control their visibility like accordion
Switch logic modules: showSingleCollapse(e) & hideSingleCollapse(e)
Beyond this there is logic for validation check like not to add same css class if it already has.
My Suggestion: There is better accordion logic out there in jQuery Accordion itself but you can refer this code to understand the real logic behind Accordion

Get numerical value from parent with id like 'post-1' and use it in jQuery function

I'm trying to figure out the following.
I have following jQuery code:
var as = "";
var bPlay = 0;
audiojs.events.ready(function() {
as = audiojs.createAll();
$(".audiojs .play-pause").click(function() {
var e = $(this).parents(".audiojs").index(".audiojs");
$.each(as, function(t, n) {
if (t != e && as[t].playing) {
as[t].pause()
}
})
bPlay = !bPlay;
if (bPlay == 1) {
$(".bar").each(function(i) {
fluctuate($(this));
});
} else {
$(".bar").stop();
}
})
});
In a nutshell it preforms list of things when someone clicks particular .audiojs instance on a page. 1) checks if there is any other instance playing, if there is pauses it. And if it is playing applies fluctuate function to elements on a page that have class="bar". This is the issue! I don't want to apply it to all .bar's on a page, but only to a specific group that is associated with particular .audiojs instance (the one that is being clicked and is playing).
I thought of the following solution. Each .audiojs instance is inside a div tag that has id like "post-1", "post-2" etc.. where numerical value is post id from database. I can add this numerical id to bar, so it would be like bar-1, bar-2 etc... However after this I'm having issues.
For javascript to work I need to retrieve numerical value from "post-[id]" associated with audiojs instance that is being clicked and than store it somehow, so I can use it like this afterwards
bPlay = !bPlay;
if (bPlay == 1) {
$(".bar-[value retrieved from post-...]").each(function(i) {
fluctuate($(this));
});
} else {
$(".bar-[value retrieved from post...]").stop();
}
Could someone explain to me how it can be achieved?
Honestly, the easiest way would be to stick it in a custom data-* attribute on the <div id="post-X"> element, like so:
<div id="post-1" data-bar="bar-1">...</div>
Then, you said your .audiojs element is inside that <div>, so just go from this inside the event handler to that <div> element (using .closest()) and get the value of it:
var barId = $(this).closest('[id^="post-"]').attr('data-bar');
Then when you need to use it:
$("." + barId).each(function(i) {
fluctuate($(this));
});
Instead of embedding the value in a class or ID, use a data-* attribute:
<div class="audiojs" data-fluctuate-target="bar-1">
<button type="button" class="play-pause">
<!-- ... -->
</button>
</div>
<div class="bar-1">
<!-- ... -->
</div>
In your click event handler, use the following to fluctuate or stop the correct elements:
var fluctuateClass = $(this).closest('.audiojs').attr('data-fluctuate-target');
$('.' + fluctuateClass).each(function () {
if (bPlay == 1) {
fluctuate($(this));
} else {
$(this).stop();
}
});

Boolean test HTML textbox input compared to array values

I am trying to create a single textbox form on a webpage to boolean test the input submitted by the user. The input will be the website user's zip code. I want to make a predetermined array of zip codes that will test true.
If it is true (the zip code entered is included in the predetermined array), I want to display one bit of HTML, and if it tests false, I want to display another bit.
I've searched around and looked in some of my JavaScript books (I've just started learning) and haven't found an answer; could someone help me out with this? Thanks!
HTML:
<label id="input-label" class="invalid">
ZIP code: <input id="zipcode" />
<div class="valid-message">
Valid
</div>
<div class="invalid-message">
Invalid
</div>
</label>
CSS:
#input-label.valid .valid-message { display: block; }
#input-label.valid .invalid-message { display: none; }
#input-label.invalid .valid-message { display: none; }
#input-label.invalid .invalid-message { display: block; }
Javascript
function isValidZip(z) {
return ['12345','67890'].indexOf(z) != -1;
}
var label = document.getElementById('input-label');
var input = document.getElementById('zipcode');
input.onkeydown = function() {
label.className = isValidZip(input.value) ? "valid" : "invalid";
}
You could try something like this(might be a little off I'll double check then get back to you :) ):
var zipArr = ['98671','97006'];//insert the zip/post codes here
var userInputEl = document.getElementById('userInput');//Get the element could use tag names or it would be better actually if you used jQuery but yeah
var elToShow = document.getElementById('elementToShowIfNotFound');
var otherElToShow = document.getElementById('idOfOtherelementToShow');
var userInput = userInputEl.value();//might be .text()
if(zipArr.indexOf(userInput) === -1){//-1 means it isn't found in the array
zipArr.push(userInput);//pushes the new one onto the array
elToShow.style.display = 'block';//this changes the css display to block instead of it being hidden.
}else{
otherElToShow.style.display= 'block';
}
Might not be the best way to do this, but I'd suggest using jQuery it makes this process a lot easier.

Categories

Resources