reduce into object orientated javascript - javascript

I have writen a little javascript. It is one of my first and I am still learning. I would like to reduce the line count and make it more efficient. I believe that Object Orientated Programming will be my best choice.
The script assigns a class to a button on hover.
gets elements with that class and performs various if functions to determine if the src attribute of the image button should be changed. The script also changes another image src attribute at the same time.
I am wondering if I can somehow condense the logic of the if statements into just one or two, then using variables perform the src attribute changes. But I dont know how to go about this...?
//assign navButtons to var buttons (creates array)
var buttons = document.getElementsByClassName("navButton");
//set buttonHover function
function buttonOn(){
if ( arrow == topArrow.mouseout ) {
newArrow = document.getElementById("topArrow");
newArrow.setAttribute("src", topArrow.mouseover);
menuText.setAttribute("src", topArrow.text);
}
if ( arrow == rightArrow.mouseout ) {
newArrow = document.getElementById("rightArrow");
newArrow.setAttribute("src", rightArrow.mouseover);
menuText.setAttribute("src", rightArrow.text);
}
if ( arrow == bottomArrow.mouseout ) {
newArrow = document.getElementById("bottomArrow");
newArrow.setAttribute("src", bottomArrow.mouseover);
menuText.setAttribute("src", bottomArrow.text);
}
if ( arrow == leftArrow.mouseout ) {
newArrow = document.getElementById("leftArrow");
newArrow.setAttribute("src", leftArrow.mouseover);
menuText.setAttribute("src", leftArrow.text);
}
}
//set buttonHover function
function buttonOff(){
if ( arrow != topArrow.mouseout ) {
resetArrow = document.getElementById("topArrow");
resetArrow.setAttribute("src", topArrow.mouseout);
menuText.setAttribute("src", start.text);
}
if ( arrow != rightArrow.mouseout ) {
resetArrow = document.getElementById("rightArrow");
resetArrow.setAttribute("src", rightArrow.mouseout);
menuText.setAttribute("src", start.text);
}
if ( arrow != bottomArrow.mouseout ) {
resetArrow = document.getElementById("bottomArrow");
resetArrow.setAttribute("src", bottomArrow.mouseout);
menuText.setAttribute("src", start.text);
}
if ( arrow != leftArrow.mouseout ) {
resetArrow = document.getElementById("leftArrow");
resetArrow.setAttribute("src", leftArrow.mouseout);
menuText.setAttribute("src", start.text);
}
}
//for each instance of buttons, assign class "active" onmouseover
for(var i = 0; i < buttons.length; ++i){
buttons[i].onmouseover = function() {
this.className = "active";
arrow = document.getElementsByClassName("active");
//get attribute
arrow = arrow[0].getAttribute("src");
console.log(arrow);
buttonOn();
};
}
//for each instance of buttons, remove class "active" onmouseout
for(var i = 0; i < buttons.length; ++i){
buttons[i].onmouseout = function () {
arrow = document.getElementsByClassName("active");
//get attribute
arrow = arrow[0].getAttribute("src");
buttonOff();
this.className = "";
};
}
Any help would be ace!
The JS Fiddle

Don't handle everything in JS, you can simply set a background image on the available anchor tags without using an img tag and then change the background image on :hover (best would be to use sprites). The only part where JS should kick in would be to change the text image, but there also not by changing an img tag src attribute.
You should preload all the text images as content (with a sensible alt text), position them over one another and then show/hide them according to what button was hovered.
Maybe I'll adjust your fiddle if I get to it but you could probably already optimize it with these information yourself.

You could improve the buttonOn function a bit, by removing the if statements and replacing them with a call to a factory class of some kind. For example:
function ArrowHandlerFactory(){
var self = this;
self.Create = function(arrow) {
alert(arrow);
if ( arrow == topArrow.mouseout ) {
return new topArrowHandler();
}
}
return {
Create: self.Create
}
}
var topArrowHandler = function(){
var self = this;
self.ArrowPressed = function() {
newArrow = document.getElementById("topArrow");
newArrow.setAttribute("src", topArrow.mouseover);
menuText.setAttribute("src", topArrow.text);
}
return {
ArrowPressed: self.ArrowPressed
}
}
var factory = new ArrowHandlerFactory();
//set buttonHover function
function buttonOn(){
var handler = factory.Create(arrow);
handler.arrowPressed();
}
The above isn't the complete code, but it gives you the basics to get started.
I'm not stating that the above is the best JavaScript ever, but the core idea of using a factory class, and then a specific class for each arrow direction, is still sound.

Related

Javascript, Click eventlistener doing the same thing with multiple classes

I'm very new to learning JavaScript, and I've tried to read, and look for similar answers, but everything is pointing at jQuery, which I want to avoid using for this problem. I can't quite work out what is jQuery and what still works in JS...
I have set up a function that can grab the innerHTML but I can't seem to assign it to the same classes, else it'll only work on the first instance, and I tried creating multiple classes but essentially they're all the same button with different values...
document.querySelector(".b1").addEventListener("click", writeDisp);
document.querySelector(".b2").addEventListener("click", writeDisp);
document.querySelector(".b3").addEventListener("click", writeDisp);
document.querySelector(".b4").addEventListener("click", writeDisp);
function writeDisp() {
if(dispNum.length < 9){
if(dispNum === "0") {
dispNum = this.innerHTML
} else {
dispNum = dispNum + this.innerHTML};
document.querySelector(".display").textContent = dispNum;
}
}
}
How can I make this more simple. As there are way more .b* classes to add, and I'd rather not have a massive list if possible.
Thanks,
var class_elem = document.querySelectorAll("button[class^='b']");
function writeDisp(){
if(dispNum.length < 9){
if(dispNum === "0"){dispNum = this.innerHTML}else{dispNum = dispNum + this.innerHTML};
document.querySelector(".display").textContent = dispNum;
}
}
for (var i = 0; i < class_elem.length; i++) {
class_elem[i].addEventListener('click', writeDisp, false);
}
//Here your code in javascript only.
If you don't want to use jquery, you can use native document.querySelectorAll API like this
function writeDisp(){
if(dispNum.length < 9){
if(dispNum === "0"){
dispNum = this.innerHTML
} else {
dispNum = dispNum + this.innerHTML
}
document.querySelector(".display").textContent = dispNum;
}
}
// this line will select all html tags that contains a class
// name starting with 'b'
var doms = document.querySelectorAll("[class^=b]");
doms.forEach(function(dom) {
dom.addEventListener('click', writeDisp);
})
Note
querySelectorAll will fetch only those DOM instance in which b* is defined as first class, so in case of multiple class defintion, it will not fetch those DOMs which don't have the desired classname at first. That means if you have a DOM defintion like <div class="a box"></div>, this will be ignored, as here, classname starting with b sits after a class.

Can you use "this" attribute on onclick of an HTML tag?

Can you use the this tag for the onclick on an HTML tag?
Here's my JS code...
function changeImage() {
this/*<-- right there <--*/.src=a;
}
document.getElementsByTagName('img').onclick = function(){
changeImage();
} ;
Am I doing something wrong?
Use it this way...
function changeImage(curr) {
console.log(curr.src);
}
document.getElementsByTagName('img').onclick = function(){
changeImage(this);
} ;
You could use the .call() method to invoke the function with the context of this.
In this case, you would use:
changeImage.call(this)
Example Here
function changeImage() {
this.src = 'http://placehold.it/200/f00';
}
document.getElementsByTagName('img')[0].onclick = function(){
changeImage.call(this);
};
As a side note, getElementsByTagName returns a live HTMLCollection of elements. You need to apply the onclick handler to an element within that collection.
If you want to apply the event listener to the collection of elements, you iterate through them and add event listeners like this:
Updated Example
function changeImage() {
this.src = 'http://placehold.it/200/f00';
}
Array.prototype.forEach.call(document.getElementsByTagName('img'), function(el, i) {
el.addEventListener('click', changeImage);
});
Or you could simplify it:
Example Here
Array.prototype.forEach.call(document.getElementsByTagName('img'), function(el, i) {
el.addEventListener('click', function () {
this.src = 'http://placehold.it/200/f00';
});
});
You are doing two things wrong.
You are assigning the event handler to a NodeList instead of to an element (or set of elements)
You are calling changeImage without any context (so this will be undefined or window depending on if you are in strict mode or now).
A fixed version would look like this:
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
images[i].onclick = function () {
changeImage.call(this);
};
}
But a tidier version would skip the anonymous function that does nothing except call another function:
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
images[i].onclick = changeImage;
}
And modern code would use addEventListener.
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
images[i].addEventListener('click', changeImage);
}
However, images are not interactive controls. You can't (by default) focus them, so this approach would make them inaccessible to people who didn't use a pointing device. Better to use controls that are designed for interaction in the first place.
Generally, this should be a plain button. You can use CSS to remove the default padding / border / background.
If you can't put a button in your plain HTML, you can add it with JS.
var images = document.getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
var image = images[i];
var button = document.createElement('button');
button.type = "button";
image.parentNode.replaceChild(button, image);
button.appendChild(image);
button.addEventListener('click', changeImage);
}
function changeImage(event) {
this.firstChild.src = a;
}

Repeat same JS function multiple times on same page

I have a large form and a JS to display a hidden DIV with a warning graphic if the user starts inputting with CAPS LOCK enabled. I have the DIV positioned to appear within the form text field. What I need, though, is to repeat this function in several separate fields, each field calling on a different class so that the warning graphic only appears in the specific field in which the user is currently typing. I'll post the JS followed by the CSS.
<script type="text/javascript">
var existing = window.onload;
window.onload = function()
{
if(typeof(existing) == "function")
{
existing();
}
loadCapsChecker();
}
function loadCapsChecker()
{
capsClass = "capLocksCheck";
capsNotice = "capsLockNotice";
var inputs = document.getElementsByTagName('INPUT');
var elements = new Array();
for(var i=0; i<inputs.length; i++)
{
if(inputs[i].className.indexOf(capsClass) != -1)
{
elements[elements.length] = inputs[i];
}
}
for(var i=0; i<elements.length; i++)
{
if(document.addEventListener)
{
elements[i].addEventListener("keypress",checkCaps,"false");
}
else
{
elements[i].attachEvent("onkeypress",checkCaps);
}
}
}
function checkCaps(e)
{
var pushed = (e.charCode) ? e.charCode : e.keyCode;
var shifted = false;
if(e.shiftKey)
{
shifted = e.shiftKey;
}
else if (e.modifiers)
{
shifted = !!(e.modifiers & 4);
}
var upper = (pushed >= 65 && pushed <= 90);
var lower = (pushed >= 97 && pushed <= 122);
if((upper && !shifted) || (lower && shifted))
{
if(document.getElementById(capsNotice))
{
document.getElementById(capsNotice).style.display = 'block';
}
else
{
alert("Please disable Caps Lock.");
}
}
else if((lower && !shifted) || (upper && shifted))
{
if(document.getElementById(capsNotice))
{
document.getElementById(capsNotice).style.display = 'none';
}
}
}
</script>
And the CSS:
#capsLockNotice {
position: relative;
display: none;
}
#capsLockNotice img {
position: absolute;
right: 5px;
top: -28px;
}
Then I put a "capLocksCheck" class on the input field, followed by this HTML:
<div id="capsLockNotice">
<img src="/images/capslock-notice.png" title="Please disable Caps Lock." alt="Please disable Caps Lock." />
</div>
What I need to do is have each of several specific form fields to call on its own unique Div Class so the warning graphic only appears in the specific field in which the user is currently typing. How can I modify the JS to allow different fields to call on different classes? I tried copying and pasting the entire code a second time, and changed
capsClass = "capLocksCheck";
capsNotice = "capsLockNotice";
to
capsClass = "capLocksCheck2";
capsNotice = "capsLockNotice2";
but obviously that didn't work. It just disabled the function entirely.
Thanks in advance for any help.
Your window.onload function is an anonymous function which calls loadCapsChecker();
window.onload = function()
{ ....
loadCapsChecker();
... //rest of your code
...
However loadCapsChecker(); was not defined as a global variable (Yes in JS variables can be functions ). Hence your window.onload function has no idea what loadCapsChecker(); refers to.
Try to declare the function loadCapsChecker(); with a global scope. It should work fine.
declare it before usage like this
var loadCapsChecker = function (){
// Your function code same as above for loadCapschecker in your code
};
window.onload = function()
{ ....
loadCapsChecker(); // Now this will because it can see the var "loadCapsChecker"
... //rest of your code
...
Hope that helps :)
besides #woofmeow answer
you can get the one in focus using document.activeElement
see How do I find out which DOM element has the focus?
as a side note
why don't you just change the active form field text? or better off just use the alert
or some pop-up div

Auto highlighting part of word

I'm trying to build a "search in the shown elements" function with jquery and css.
Here's what I got so far:
http://jsfiddle.net/jonigiuro/wTjzc/
Now I need to add a little feature and I don't know where to start. Basically, when you write something in the search field, the corresponding letters should be highlighted in the list (see screenshot, the blue highlighted part)
Here's the script so far:
var FilterParticipants = function(options) {
this.options = options;
this.participantList = [];
this.init = function() {
var self = this;
//GENERATE PARTICIPANTS OPBJECT
for(var i = 0; i < this.options.participantBox.length ; i++) {
this.participantList.push({
element: this.options.participantBox.eq(i),
name: this.options.participantBox.eq(i).find('.name').text().toLowerCase()
})
}
//ADD EVENT LISTENER
this.options.searchField.on('keyup', function() {
self.filter($(this).val());
})
}
this.filter = function( string ) {
var list = this.participantList;
for(var i = 0 ; i < this.participantList.length; i++) {
var currentItem = list[i];
//COMPARE THE INPUT WITH THE PARTICIPANTS OBJECT (NAME)
if( currentItem.name.indexOf(string.toLowerCase()) == -1) {
currentItem.element.addClass('hidden');
} else {
currentItem.element.removeClass('hidden');
}
}
}
this.init();
}
var filterParticipants = new FilterParticipants({
searchField: $('#participants-field'),
participantBox: $('.single_participant'),
nameClass: 'name'
});
I think you're just complicating things too much... You can do this easily in a few lines. Hope this helps:
var $search = $('#participants-field');
var $names = $('.single_participant p');
$search.keyup(function(){
var match = RegExp(this.value, 'gi'); // case-insensitive
$names
.hide()
.filter(function(){ return match.test($(this).text()) })
.show()
.html(function(){
if (!$search.val()) return $(this).text();
return $(this).text().replace(match, '<span class="highlight">$&</span>');
});
});
I used hide and show because it feels snappier but you can use CSS3 animations and classes like you were doing.
Demo: http://jsfiddle.net/elclanrs/wTjzc/8/
Here`s the way to do it with jQuery autocomplete so question
If you want to build it on your own you can do the following:
1. Get the data of every item.
2. Make render function in which you will substitute say "Fir" in Fire word to Fire
3. Every time you change the text in the input you can go through the items and perform substitution.

jquery jqgrid treegrid expand to a specific level

This is a question for jQuery jqGrid. We need a function to expand a treegrid to a certain level. I tried directly using collapseRow, expandRow, collapseNode and expandRow. But, the collapseRow/expandRow are recursive. So, it was really slow to call these functions at every row. Therefore, I added hideRow and showRow functions to jqgrid. I succeeded expand and collapse the tree to a certain level. However, when tree is expanded say to level 3, if you close your tree by clicking the triangle at the top level. Some expanded rows are still there.
This is the functions I added under jqgrid.
hideRow: function (record) {
this.each(function(){
$( this.rows.namedItem(record.id)).css("display","none");
});
},
showRow: function (record) {
this.each(function(){
$( this.rows.namedItem(record.id)).css("display","");
});
},
This is how I called these functions. (I omitted some contexts, but that shouldn't be the road block.)
var len = me.gjson.datastr[me.reader_root].length;
for (var i=len-1; i>-1; i--) {
var one_node = jQuery(me.gid).getInd(i+1,true);
one_node._id_ = one_node.id;
if (parseInt(me.gjson.datastr[me.reader_root][i].level)<me.expand_level) {
jQuery(me.gid).jqGrid('expandNode',one_node);
} else {
jQuery(me.gid).jqGrid('collapseNode',one_node);
}
}
for (var i=0; i<len; i++) {
var one_node = jQuery(me.gid).getInd(i+1,true);
one_node._id_ = one_node.id;
if (parseInt(me.gjson.datastr[me.reader_root][i].level)<me.expand_level+1) {
jQuery(me.gid).jqGrid('showRow',one_node);
} else {
jQuery(me.gid).jqGrid('hideRow',one_node);
}
}
I traced into the jqGrid code. It shows that the "expanded" value was set correctly within collapseNode/expandNode. But, when you click the triangle at top level to collapse the whole tree, value "expanded" was set to something else. So, the question is what could be the cause? Thanks in advance.
Got a dirty solution myself. I added 2 lines in collapseNode and expandNode.
expandNode : function(rc) {
return this.each(function(){
if(!this.grid || !this.p.treeGrid) {return;}
var expanded = this.p.treeReader.expanded_field,
parent = this.p.treeReader.parent_id_field,
loaded = this.p.treeReader.loaded,
level = this.p.treeReader.level_field,
lft = this.p.treeReader.left_field,
rgt = this.p.treeReader.right_field;
if(!rc[expanded]) {
var id = $.jgrid.getAccessor(rc,this.p.localReader.id);
var rc1 = $("#"+$.jgrid.jqID(id),this.grid.bDiv)[0];
var position = this.p._index[id];
if( $(this).jqGrid("isNodeLoaded",this.p.data[position]) ) {
rc[expanded] = true;
this.p.data[position][expanded] = true; // <--- add this line in jqgrid.src.js
$("div.treeclick",rc1).removeClass(this.p.treeIcons.plus+" tree-plus").addClass(this.p.treeIcons.minus+" tree-minus");
} else if (!this.grid.hDiv.loading) {
rc[expanded] = true;
$("div.treeclick",rc1).removeClass(this.p.treeIcons.plus+" tree-plus").addClass(this.p.treeIcons.minus+" tree-minus");
this.p.treeANode = rc1.rowIndex;
this.p.datatype = this.p.treedatatype;
if(this.p.treeGridModel == 'nested') {
$(this).jqGrid("setGridParam",{postData:{nodeid:id,n_left:rc[lft],n_right:rc[rgt],n_level:rc[level]}});
} else {
$(this).jqGrid("setGridParam",{postData:{nodeid:id,parentid:rc[parent],n_level:rc[level]}} );
}
$(this).trigger("reloadGrid");
rc[loaded] = true;
if(this.p.treeGridModel == 'nested') {
$(this).jqGrid("setGridParam",{postData:{nodeid:'',n_left:'',n_right:'',n_level:''}});
} else {
$(this).jqGrid("setGridParam",{postData:{nodeid:'',parentid:'',n_level:''}});
}
}
}
});
},
collapseNode : function(rc) {
return this.each(function(){
if(!this.grid || !this.p.treeGrid) {return;}
var expanded = this.p.treeReader.expanded_field;
if(rc[expanded]) {
rc[expanded] = false;
var id = $.jgrid.getAccessor(rc,this.p.localReader.id);
this.p.data[this.p._index[id]][expanded] = false; // <--- add this line in jqgrid.src.js
var rc1 = $("#"+$.jgrid.jqID(id),this.grid.bDiv)[0];
$("div.treeclick",rc1).removeClass(this.p.treeIcons.minus+" tree-minus").addClass(this.p.treeIcons.plus+" tree-plus");
}
});
},
BTW, don't ask me why we need these 2 lines. I don't know myself. It's like a housewife fixed a short circuit by just matching the wires with colors. But, she might not know what short circuit means.

Categories