Hi I am very new to javascript and I have made a lot of buttons on a site which have different calls. They are all the same minus the results which are pretty much like hide.1 and show.2 etc. Its pretty big but there must be a way to make it smaller? Its no biggy just bad coding that i would like not to get in the habbit of doing. And the only reason for (a) after some of them is that the call doesnt works twice, so when i set it show in a full size screen and a mobile screen it does not work right? Thanks
The code is :
$(document).ready(function() {
$('#btn-continue').on('click', function() {
$('#loginbox').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next1').on('click', function() {
$('#1box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next2').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next2a').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2a').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next3').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next3a').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3a').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next4').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-next4a').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4a').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back5').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back5a').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
And the calls work like this :
<div class="col-sm-6 controls hidden-xs">
<div><button id='btn-back2' name ='back2' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-xs"><button id='btn-next2' name ='next2' type='button' class='btn btn-primary'>Next</button></div>
</div>
<div class="col-xs-6 controls hidden-sm hidden-md hidden-lg">
<div><button id='btn-back2a' name ='back2a' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-sm hidden-md hidden-lg"><button id='btn-next2a' name ='next2a' type='button' class='btn btn-primary'>Next</button></div
>
Just make it more generic. You can use classes or data attributes. There is a lot of different ways to do this.
jsfiddle - https://jsfiddle.net/vouvcedj/4/
There is still a lot of cleanup that can be done but this should be pretty clear as to what you can do.
JS
$(function() {
var handleFirstLast = function() {
if ($('.shown').is('.section:first')) {
$('#btn-back').hide();
} else {
$('#btn-back').show();
}
if ($('.shown').is('.section:last')) {
$('#btn-forward').hide();
} else {
$('#btn-forward').show();
}
}
handleFirstLast();
$('#btn-back').on('click', function(e) {
// find the currently shown section and get the previous
// https://api.jquery.com/next/
var $showing = $('.section.shown');
$showing.prev().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
$('#btn-forward').on('click', function(e) {
var $showing = $('.section.shown');
$showing.next().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
});
HTML
<div class="sections">
<div class="section shown">
section 1
</div>
<div class="section hidden">
section 2
</div>
<div class="section hidden">
</div>
</div>
<div class="actions">
<button id="btn-back">Back</button>
<button id="btn-forward">Forward</button>
</div>
Perhaps something like this would make sense:
$(document).ready(function() {
const buttons = [
['btn-continue', 'loginbox', '1box'],
['btn-next1', '1box', '2box'],
['btn-next2', '2box', '3box'],
['btn-back2', '2box', '1box'],
['btn-next2a', '2box', '3box'],
['btn-back2a', '2box', '1box'],
['btn-next3', '3box', '4box'],
['btn-back3', '3box', '2box'],
['btn-next3a', '3box', '4box'],
['btn-back3a', '3box', '2box'],
['btn-next4', '4box', '5box'],
['btn-back4', '4box', '3box'],
['btn-next4a', '4box', '5box'],
['btn-back4a', '4box', '3box'],
['btn-back5', '5box', '4box'],
['btn-back5a', '5box', '4box']
]
buttons.forEach(function(b) {
$('#' + b[0]).on('click', function() {
$('#' + b[1]).hide();
$('#' + b[2]).show();
});
});
});
This captures the repetitive data in a single structure, and replaces the repetitive code with a loop.
Note that this is entirely untested.
This refers to DRY (Don't Repeat Yourself) principle and it is one of the core principles in programming. If you find yourself repeating in code, means trouble, a small change can be catastrophic for you if you need to go to each place and implement it.
With DRY principle you write code once and reuse it via numerous techniques. A change can be applied once and to one place only.
When the DRY principle is applied successfully, a modification of any
single element of a system does not require a change in other
logically unrelated elements. Additionally, elements that are
logically related all change predictably and uniformly, and are thus
kept in sync.
You code can be changed to something like this:
var myNs = myNs || {};
$(document).ready(function() {
$(myNs.map).each(function(index, value) {
$(value.clickedElement).on('click', clickHandler.bind(this, value));
});
function clickHandler(value) {
$(value.hideElement).hide();
$(value.showElement).show();
}
});
What about the myNs? This is a configuration object, a namespace to load your configuration for elements. This can be a completely different JavaScript file, which of course is loaded before this particular script.
Example:
// This can be in a completely different file
var myNs = {
map: [{
clickedElement: '#btn-continue',
hideElement: '#loginbox',
showElement: '#1box'
}, {
clickedElement: '#btn-next1',
hideElement: '#1box',
showElement: '#2box'
}, {
clickedElement: '#btn-next2',
hideElement: '#2box',
showElement: '#3box'
}, {
clickedElement: '#btn-back2',
hideElement: '#2box',
showElement: '#1box'
},
...
]
};
You can take this a step forward and use the new ES6 modules or even AMD modules. Of course you need a module loader for that, like SystemJS, Webpack or RequireJS.
Check out the following plunk to peak on some code.
Related
I am working with an html component that appears twice on a page.
The issue I am having is that when I click one component to toggle it's content (like an accordion) both components toggle simultaneously.
How can I rewrite the JS to toggle each component when I click them separately, without giving the components different classes and rewriting the js twice?
This is what I have so far;
var bindEventsToUI = function () {
$(".details").click(function(e){
$(".content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
};
Try this:
$(".details").click(function(e) {
$(this).children(".content").slideToggle("slow");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='details'>Testing 123
<div class='content'>> Content 123</div>
</div>
<div class='details'>Testing 456
<div class='content'>> Content 456</div>
</div>
You will need to have a different identifier for each element. I would recommend an id:
var bindEventsToUI = function () {//element) {
$("#id1 .details").click(function(e){
$("#id1 .content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
$("#id2 .details").click(function(e){
$("#id2 .content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
//..and so on
};
Or, you could use the closest function:
var bindEventsToUI = function () {//element) {
$(".details").click(function(e){
$(this).closest(".content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
};
As seen below, one function shows two things and the other function hides those two things.
Somehow they are so similar that I really want to see if there's a way I can combine them together but I just couldn't think of a way to do it.
var btns = {
filterBtn: function () {
$('.filter-btn').on('click', function () {
$('.cover').show();
$('.popup').show();
})
},
popupClose: function () {
$('.popup-x-btn').on('click', function () {
$('.cover').hide();
$('.popup').hide();
})
}
}
Anyone has any suggestions?
Or it's the best to just leave it as is?
The best refactoring I can think of would be to use jQuery.toggle(), which you can use true to show the element or false to hide it:
$('.showImage, .hideImage').on('click', function() {
$('img').toggle($(this).hasClass('showImage'));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="showImage">Show</button>
<button class="hideImage">Hide</button>
<br>
<img src="https://placeimg.com/250/250/any">
Or in your situation:
$('.filter-btn, .popup-x-btn').on('click', function() {
$('.cover, .popup').toggle($(this).hasClass('filter-btn'));
});
Its JS, you have so many possibilities:
function toggle(selector, dir) {
return function() {
$(selector).on('click', function() {
$('.cover')[dir]();
$('.popup')[dir]();
});
};
}
var btns = {
filterBtn: toggle('.filter-btn', 'show'),
popupClose: toggle('.popup-x-btn', 'hide'),
}
But is this better? Well, thats a personal opinion.
The important thing about JS is that you have so many ways to do everything, that the difficult thing is to find a consistent way to do things. Thats why people use more complex frameworks like react, angular, ember, vue, and so many more.
You can take advantage of jQuery's toggle method.
function toggleCoverAndPopup(showOrHide) {
$('.cover, .popup').toggle(showOrHide);
}
var btns = {
filterBtn: function() {
$('.filter-btn').on('click', function() {
toggleCoverAndPopup(true);
})
},
popupClose: function() {
$('.popup-x-btn').on('click', function() {
toggleCoverAndPopup(false);
})
}
}
you can do this with a simple if else statement.
$('.filter-btn .popup-x-btn').on('click', function() {
if ($(this).hasClass("filter-btn")) {
$('.cover').show();
$('.popup').show();
} else {
$('.cover').hide();
$('.popup').hide();
}
});
Im trying to build some widgets and want to separate the html template from the widget definition. Im new to jQuery and want to evaluate if we can use it for a small read only web application.
What I've down so far:
$(function() {
var load = function() {
var div = $("<div>");
div.testwidget({imgUrl:"http://foo.com/bar.gif"});
$( "#root" ).append(div);
};
var html;
$.get("testwidget.html", function(data) {
html = data;
load();
}, "html");
$.widget("custom.testwidget", {
_create: function() {
var content = html;
var photo = $(content).find(".photo");
photo.attr("src", "http://foo.com/bar.gif");
//photo.attr("src", function() {return this.imgUrl});
this.element.append(content);
}
});
});
In the file testwidget.html is a template like:
<div>
<div class="hit-image">
<img class="photo" />
</div>
</div>
It works to load the html and reuse it for the widget creation. I dont know if that is good for performance but I think better than loading the html on every widget creation.
What doesn't work is setting the src attribute on the img tag. Its just not present after appending the widget. Can anyone tell me what Im doing wrong?
Its just a proof of concept, but Im happy about tips how to improve my code structure.
I don't see where you define the options within your widget. I would expect something like:
$.widget("custom.testwidget", {
options: {
imgUrl: "http://foo.com/bar.gif"
},
_create: function() {
$.get("testwidget.html", function(r){
$(r).find(".photo").attr("src", this.options.imgUrl);
this.element.append(r);
});
}
});
Then, would use it like so:
var div = $("<div>")
.appendTo("#root")
.testwidget({ imgUrl: "http://foo.com/bar.gif" });
console.log("Loaded " + div.testwidget("imgUrl"));
Personally, I would just just make the template in the widget versus calling an external/alternate resource. Something like:
$.widget("custom.testwidget", {
options: {
imgUrl: "http://foo.com/bar.gif"
},
_create: function() {
var img = $("<img>", { class: "photo", src: this.options.imgUrl });
var wrap = $("<div>", { class: "hit-image" });
wrap.append(img);
this.element.append(wrap);
}
});
Working Example: https://jsfiddle.net/Twisty/wv6kwzpf/
Been looking to figure out how with Twitter Flight can attach to dynamic created elements.
Having the following HTML
<article>Add element</article>
And the following component definition
var Article = flight.component(function () {
this.addElement = function () {
this.$node.parent().append('<article>Add element</article>');
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
Once a new element is created, the click event doesn't fire. Here's the fiddle: http://jsfiddle.net/smxx5/
That's not how you should be using Flight imho.
Each component should be isolated from the rest of the application, therefore you should avoid this.$node.parent()
On the other hand you can interact with descendants.
My suggestion is to create an "Articles manager" component that uses event delegation.
eg.
http://jsfiddle.net/kd75v/
<div class="js-articles">
<article class="js-article-add">Add element</article>
<div/>
and
var ArticlesManager = flight.component(function () {
this.defaultAttrs({
addSelector: '.js-article-add',
articleTpl: '<article class="js-article-add">Add element</article>'
});
this.addArticle = function () {
this.$node.append(this.attr.articleTpl);
};
this.after('initialize', function () {
this.on('click', {
addSelector: this.addArticle
});
});
});
ArticlesManager.attachTo('.js-articles');
Try attaching Article to each new article added:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/smxx5/2/
var Article = flight.component(function () {
this.addElement = function () {
var newArticle = $('<article>Add element</article>');
this.$node.parent().append(newArticle);
Article.attachTo(newArticle);
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
The Article.attachTo('article'); at the end, that runs once on load, will only attach to existing article elements.
I hit this problem, and worked around is as follows...
Javascript: All thrown together for brevity, but could easily be separated.
(function(){
var TestComponent, LoaderComponent;
TestComponent = flight.component(function() {
this.doSomething = function()
{
console.log('hi there...');
};
this.after('initialize', function() {
this.on('mouseover', this.doSomething);
});
});
LoaderComponent = flight.component(function() {
this.attachComponents = function()
{
TestComponent.attachTo('.test');
};
this.after('initialize', function() {
// Initalise existing components
this.attachComponents();
// New item created, so re-attach components
this.on('newItem:testComponent', this.attachComponents);
});
});
LoaderComponent.attachTo('body');
}());
HTML: Note that one .test node exists. This will be picked up by Flight on initialization (i.e. not dynamic). We then add a second .test node using jQuery and fire off the event that the LoaderComponent is listening on.
<div class="test">
<p>Some sample text.</p>
</div>
<script>
$('body').append('<div class="test"><p>Some other text</p></div>').trigger('newItem:testComponent');
</script>
This is obviously a very contrived example, but should show that it's possible to use Flight with dynamically created elements.
Hope that helped :)
I am new to Jasmine and seem to be struggling to get what I think is a fairy standard kind of thing running.
I am loading an HTML file via a fixture and trying to call a click on an element on the dom. This I would expect result in the call to the method of the JS file I am trying to test. When I try and debug this in developer tools the method that should be called in my js file never hits a breakpoint. As such I assume that code is not being called and therfore does not toggle the expand/collapse class.
My test:
describe("userExpand", function () {
beforeEach(function () {
loadFixtures('user-expand.html');
//userControl();
//this.addMatchers({
// toHaveClass: function (className) {
// return this.actual.hasClass(className);
// }
//});
});
//this test works ok
it("checks the click is firing", function () {
spyOnEvent($('.expanded'), 'click');
$('.expanded').trigger('click');
expect("click").toHaveBeenTriggeredOn($('.expanded'));
});
//this doesn't
it("checks the click is changing the class", function () {
//spyOnEvent($('.collapsed'), 'click');
var myElement = $('.collapsed');
myElement.click();
expect(myElement).toHaveClass('.expanded');
});
Part of the fixture:
<div class="wrapper">
<div class="row group">
<div class="col-md-1" data-bordercolour=""> </div>
<div class="collapsed col-md-1"> </div>
<div class="col-md-9">None (1)</div>
The JS I am trying to test:
var userControl = function () {
"use strict";
var collapse = '.collapsed';
var expand = '.expanded';
var userList = $(".userList");
function toggleState() {
var currentControl = $(this);
if (currentControl.hasClass('all')) {
if (currentControl.hasClass('expanded')) {
toggleIcon(currentControl, collapse);
userList.find(".user-group-summary").hide()
.end()
.find(".user-group-info").show();
} else {
toggleIcon(currentControl, expand);
userList.find(".user-group-summary").show()
.end()
.find(".user-group-info").hide();
}
} else {
currentControl.parent().nextUntil('.group').toggle();
currentControl.toggleClass("expanded collapsed");
currentControl.parent().find(".user-group-summary").toggle()
.end()
.find(".user-group-info").toggle();
}
};
function toggleIcon(ctrl, currentState) {
var details = ctrl.closest('div.row').siblings('.wrapper');
details.find(currentState).toggleClass('expanded collapsed');
if (currentState === expand) {
details.find('.detail').hide();
} else {
details.find('.detail').show();
}
}
userList.on('click', '.expanded, .collapsed', toggleState);
$('[data-bordercolour]').each(function () {
$(this).css("background-color", $(this).data('bordercolour'))
.parent().nextUntil('.group')
.find('>:first-child').css("background-color", $(this).data('bordercolour'));
});
return {
toggleState: toggleState
};
}();
The code works fine in normal use so I am sure I am missing something obvious with the way Jasmine should be used. Any help would be appreciated.
Update:
I can make the togglestate method fire by using call in the test rather than triggering a click event:
it('checks on click of icon toggles that icon', function () {
var myElement = $('.collapsed');
userControl.toggleState.call(myElement);
expect(myElement).toHaveClass('expanded');
});
This seems a little strange as all the examples I can find are quite happy with click. Gets me off the hook but I would still like to know what I am missing.
It's hard to give a precise hint without the source code. Does click on .collapsed involve asynchronous action(s)? If so, wrapping the test in runs(...); waitsFor(...); runs(...); may solve the problem. Check the Jasmine introduction for how to do this.