3
\$\begingroup\$

I've created a web page based on a sample from a book. It works fine, but seems to have become too complicated.

'use strict';
function toggle (triggers, elements, per_trigger, events) {
 function toggle(index, per, elem, func) {
 for (var i = 0; i < per; i++)
 func(elem[index+i]);
 }
 if ((elements.length % triggers.length != 0 && elements.length != triggers.length * per_trigger)
 || (elements.length == 0 || triggers.length == 0)
 || (events[0].func == undefined || events[0].ev == undefined)) {
 console.error('toggle: number of elements must be divisible by' +
 'number of triggers, because one trigger can toggle more than one element\n' +
 'for example: if you want a trigger to toggle two elements, then the per_trigger' +
 ' argument will be 2 and the number of elements must be equal to the number of triggers * 2');
 return false;
 }
 for (var i = 0; i < triggers.length; i++) {
 for (var f = 0; f < events.length; ++f) {
 (function(_i, _f) {
 $(triggers[_i]).on(events[_f].ev, function() {
 toggle(_i*per_trigger, per_trigger, elements, events[_f].func);
 })
 })(i, f);
 }
 }
 return true;
}
var hidden, trigger, PER_TRIGGER = 1, onclick;
function defineTriggers(hide) {
 hidden = document.getElementsByTagName('div');
 trigger = document.getElementsByTagName('h2');
 if (hide) {
 // hiding elements ..
 for (var r=0; r<hidden.length; ++r) {
 if (hidden[r].displayinfo == undefined)
 hidden[r].displayinfo = hidden[r].style.display;
 hidden[r].style.display = 'none';
 hidden[r].shown = false;
 }
 toggle(trigger, hidden, PER_TRIGGER, [{'ev': 'click', 'func': onclick}]); // defined externally ..
 }
}
function addDiv(date, info) {
 var d = document.createElement('div'),
 s = document.createElement('h2'),
 p = document.createElement('p');
 p.textContent = info;
 s.textContent = date;
 document.body.appendChild(s);
 document.body.appendChild(d);
 d.appendChild(p);
 d.style.display = 'none';
 d.shown = false;
 d.displayinfo = 'block';
 hidden[hidden.length] = d;
 trigger[trigger.length] = s;
 toggle([s], [d], PER_TRIGGER, [{'ev': 'click', 'func' : onclick}]);
}
 alert('running')
 onclick = function(e) {
 e.style.display = e.shown ? 'none' : e.displayinfo;
 e.shown = !e.shown;
 };
 defineTriggers(true);
 // adding btn
 var b = document.createElement('button'),
 f = document.getElementsByTagName('footer')[0];
 f.appendChild(b);
 b.textContent = 'Add a Date'
 b.onclick = function() {
 var day, info, day_regex = /[A-Z][a-z]{1,7} [1-9][0-9]?, 2[0-9]{3}/;
 do {
 day = prompt('Day');
 if (!day_regex.test(day)) alert('Day not correct .. !');
 } while(!day_regex.test(day))
 info = prompt('Info');
 addDiv(day, info);
 }

Is it possible to improve the clarity of this code, without affecting its functionality?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jul 29, 2014 at 0:40
\$\endgroup\$
9
  • 1
    \$\begingroup\$ Can you provide a jsbin giving an example of the functionality. Also what don't you like about your code now -- anything specific? \$\endgroup\$ Commented Jul 29, 2014 at 1:38
  • 1
    \$\begingroup\$ Also, the first suggestion I have skimming the code is use jQuery more as it appears you have it already included in the page based on the on handler \$\endgroup\$ Commented Jul 29, 2014 at 1:40
  • \$\begingroup\$ @megawac: Here .. on JSFiddle \$\endgroup\$ Commented Jul 29, 2014 at 17:47
  • \$\begingroup\$ @megawac: I'm not really knowledgeable about jquery, I'm trying to focus on javascript fundamentals at the moment .. That's seems like a good idea, nonetheless .. Can you give me some examples ? \$\endgroup\$ Commented Jul 29, 2014 at 17:54
  • \$\begingroup\$ When you don't add a date and press cancel you go to an endless loop. \$\endgroup\$ Commented Aug 10, 2014 at 6:18

1 Answer 1

1
\$\begingroup\$

I wrote this up using a lot more jQuery as an example. Confirmed working JSFiddle here.

You can see there's a considerably less lines of code, there's less boilerplate on each line (things like document.getElementsByTagName), and at least in my opinion it's a lot more expressive.

Additionally, it becomes much easier to do other things that jQuery does well. For example, to animate these toggles. Just change $hidden.toggle() to $hidden.slideToggle()

Full code:

'use strict';
function enterData(){
 var day, info, day_regex = /[A-Z][a-z]{1,7} [1-9][0-9]?, 2[0-9]{3}/;
 do {
 day = prompt('Day');
 if (!day_regex.test(day)) alert('Day not correct .. !');
 } while(!day_regex.test(day))
 info = prompt('Info');
 addDiv(day, info);
}
function bindEventsToEl( $trigger, $hidden, events ){
 //This lets you pass in a different hash of events to bind, if you wanted to
 var eventsHash = events || {
 click: function(){
 $hidden.toggle();
 }
 };
 for ( var event_type in eventsHash ){
 $trigger.on( event_type, eventsHash[event_type] );
 }
}
function defineTriggers(hide) {
 var $hidden = $("div"),
 $triggers = $("h2");
 if (hide) {
 $hidden.hide();
 $triggers.each(function(i,el){
 bindEventsToEl( $(el), $( $hidden[i] ) );
 });
 }
}
function addDiv(date, info) {
 //Creating your empty elements and inserting content
 var $header = $("<h2>").text(date),
 $p = $("<p>").text(info),
 $div = $("<div>").append($p).hide();
 //You can chain appends to the root selector
 $("body").append($header).append($div);
 bindEventsToEl( $header, $div );
}
alert('running')
// bind all of our initial header/div combos
defineTriggers(true);
// create the button to add new sections
var $button = $("<button>").text("Add a Date").appendTo("footer").on( "click", enterData );

I removed your PER_TRIGGER, per_trigger variables and the if check around binding event listeners based on their count. It didn't seem to apply in your current JSfiddle anyway, and the reasoning behind it seemed a little shaky.

However, I think this problem might be solved just by using jQuery. Check out the bindEventsToEl function. Because $trigger and $hidden are jQuery selectors, you can have any number of elements in them.

For example, we can call this:

bindEventsToEl( $("h2, div"), $("div") );

This causes ALL divs to toggle whenever ANY h2 or div is clicked. Not exactly useful, but I think it shows you how powerful and expressive jQuery can be when selecting and binding events.

You can pass in a different event hash to the bindEventsToEl as well:

bindEventsToEl( $("h2"), $("div") , {
 click: function(){ console.log("bonus click"); },
 mouseover: function(){ console.log("mouseover"); }
});

If you're still not convinced that jQuery is the route you want to go, some recommendations for your prior code:

  • Focus on simplifying your toggle function so that it's much more clear what it's doing.
  • Try not to have as much nesting inside functions, break them out into their component parts.
  • Definitely don't have another function named 'toggle' inside a function named 'toggle'. It makes it look like it's calling itself recursively.
  • Give the rest of your variables slightly clearer names as well.
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
answered Aug 10, 2014 at 4:26
\$\endgroup\$
1
  • \$\begingroup\$ I didn't want to use slideToggle because it doesn't affect opacity, so I decided to create one .. Anyways, I've put a link to the final page where I used lots of your code there, thanks .. \$\endgroup\$ Commented Aug 10, 2014 at 20:14

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.