I have the following html structure in my web application.
This is a group of <div>s which contain information about items. Specifically there are two <span> tags with pending and done quantities (integer values).
Clicking on other page elements will modify pending and done values.
Is it possible to track those changes and apply a class to the whole <div> when those values are the same?
So the whole <div id="{{ item.id }}"> will have a green background when pending and done match and no color background when values do not match. The actual action to be done is not relevant, what I miss is understanding the right approach in jQuery to monitor values in two html fields.
<div id="{{ item.id }}" href="#" state="{{ item.state }}" class="item">
<div class="row">
<div class="col-xs-4">
<span name="pending">{{ item.pending }}</span>
</div>
<div class="col-xs-4">
<span name="done">{{ item.done }}</span>
</div>
</div>
</div>
Use find and text functions along with these selectors '[name="pending"]' '[name="done"]'.
To listen to javascript modifications (jQuery), an alternative is to override the .val function.
var originalValFn = jQuery.fn.val;
jQuery.fn.val = function(value) {
originalValFn.apply(this, arguments);
if (value !== undefined) {
$('#done').trigger('input');
$('#pending').trigger('input');
}
};
Look at this code snippet.
The function updateSection must be called when the user changes the values.
var target = $('#myId');
var originalValFn = jQuery.fn.val;
jQuery.fn.val = function(value) {
originalValFn.apply(this, arguments);
if (value !== undefined) {
$('#done').trigger('input');
$('#pending').trigger('input');
}
};
var updateSection = function() {
var pending = target.find('[name="pending"]').text();
var done = target.find('[name="done"]').text();
if (pending === done) {
target.addClass('green-class');
target.removeClass('red-class');
} else {
target.addClass('red-class');
target.removeClass('green-class');
}
}
$('#pending').on('input', function(e) {
target.find('[name="pending"]').text(e.target.value);
updateSection();
});
$('#done').on('input', function(e) {
target.find('[name="done"]').text(e.target.value);
updateSection();
});
$('#done').val('3');
$('#pending').val('4');
//updateSection();
.green-class {
background-color: green;
}
.red-class {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myId" href="#" state="{{ item.state }}" class="item">
<div class="row">
<div class="col-xs-4">
<span name="pending">222</span>
</div>
<div class="col-xs-4">
<span name="done">222</span>
</div>
</div>
</div>
<hr>
<p>Enter values to test:</p>
<input id='pending' placeholder='pending'>
<input id='done' placeholder='done'>
Resources
.find()
.text()
.on()
Sure. Place an input element inside each span and have the values actually be stored in the input. Then, set up input event handlers for those input elements that simply compare the values of the inputs. If they are the same. Apply a class.
// Get inputs
var $pending = $(".pending");
var $done = $(".done");
// Set up event handler
$pending.on("input", test);
$done.on("input", test);
function test(){
if($pending.val() === $done.val()){
$("div#parent").addClass("match");
} else {
$("div#parent").removeClass("match");
}
}
input { outline:none; border:none; }
.pending { background-color:rgba(255, 100, 100, .5); }
.done { background-color:rgba(255, 255, 100, .5); }
.match { background-color:rgba(100, 255, 100, .5); }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>For testing, type different (and then the same) values into the two fields</h1>
<div id="parent" href="#" state="{{ item.state }}" class="item">
<div class="row">
<div class="col-xs-4">
<span name="pending"><input class="pending"></span>
</div>
<div class="col-xs-4">
<span name="done"><input class="done"></span>
</div>
</div>
</div>
First: the "name" attribute only applies to input elements, you probably want to use the "id" attribute.
I would also suggest creating a function to check for equality, something like this:
var pendingDone = function(){
if($("#pending").text() === $("#done").text()) {
$("#pending").addClass("green");
$("#done").addClass("green");
} else {
$("#pending").removeClass("green");
$("#done").removeClass("green");
}
}
I would suggest just calling this function in all instances that modify the values, since the change and input listener suggested in another answer will not fire if the value is modified by javascript instead of user interaction.
Related
I'm trying to add a Class to an img when the mouse is over an element and remove the class when the mouse is no longer over the element. I'm using object literal notation. I can't see to select the correct image, can anyone see where i'm going wrong?
let Cc = {
bindEvent: function() {
$('.title.em-below').hover( function() {
let selectedtitle = $(this);
Cc.scaleThumbnail(selectedtitle);
})
},
scaleThumbnail: function(selectedtitle) {
let $thumbnail = selectedtitle.siblings('.image-thumbnail')
let img = $thumbnail.children('img');
console.log(img);
img.addClass('thumbnail-active');
img.removeClass('thumbnail-active');
},
}
.thumbnail-active {
transform: scale(1.1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="title em-below">
Title
</div>
<a class="image-thumbnail">
<div class="wide-thumbnail em-below">
<img src="https://via.placeholder.com/350x150"/>
</div>
</a>
</div>
You need to call CC.bindEvent() to bind the event handler. And your hover function needs to toggle the class, not add it and then immediately remove it.
The img element is not a child of $thumbnail, it's the grandchild. Use .find() instead of .children().
let Cc = {
bindEvent: function() {
$('.title.em-below').hover( function() {
let selectedtitle = $(this);
Cc.scaleThumbnail(selectedtitle);
})
},
scaleThumbnail: function(selectedtitle) {
let $thumbnail = selectedtitle.siblings('.image-thumbnail')
let img = $thumbnail.find('img');
//console.log(img.attr('src'));
img.toggleClass('thumbnail-active');
},
}
Cc.bindEvent();
.thumbnail-active {
transform: scale(1.1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="title em-below">
Title
</div>
<a class="image-thumbnail">
<div class="wide-thumbnail em-below">
<img src="https://via.placeholder.com/350x150"/>
</div>
</a>
</div>
The problem is with this line, because of which the img tag is not captured.
$thumbnail.children('img');
.children only traverses its immediate child which is .wide-thumbnail.em-below.
Use .find instead
$thumbnail.find('img');
I am creating a news feed with VueJS and I have run into a bit of a problem with rendering the content. The API I am using sadly I am unable to change to suit my need properly at this time. The API gives me all the content already in HTML tags and it can also include images and lists and all the other basics. What I want to do is create a "read more" section which will render the first 20 words if just the text of the first "p" tag and stop there.
Does anyone know a quick and efficient way of doing this with JS?
My current display VueJS render is the following:
<div v-for="news_item in news_items">
<div v-bind:class="{ 'col-md-4': display}">
<div class="card">
<div class="header">
<h2>
{{news_item.title}} <small>{{news_item.subtitle}}</small>
</h2>
</div>
<div class="body" style="padding-top: 0">
<div class="row" style="margin-right: -20px; margin-left: -20px;">
<div class="col-md-12"
style="padding-left: 0px; padding-right: 0px;">
<img :src="news_item['thumbnail']"
class="img-responsive smaller-img" alt=""
style=" margin: 0 auto; max-height: 250px;">
</div>
</div>
<div class="row">
<div class="col-md-12">
<div v-html="news_item.content"></div>
</div>
</div>
</div>
</div>
</div>
</div>
This is the perfect time to use a directive:
https://v2.vuejs.org/v2/guide/custom-directive.html
See the codepen here: https://codepen.io/huntleth/pen/GOXaLo
Using the trim directive, you can change the content of the element. In the example above, it will show the first 5 words followed by an ellipsis.
If you're just after a pure js solution, this should do it:
var resultString = str.split(' ').slice(0, 20).join(" ");
You could use the trim directive and search the el for any p tags, and then change their content accordingly.
You don't appear to have tried anything yet, so I'll just give you these pointers. If you run into specific problems, ask again.
Make a component
The component should receive the html as a prop
The component should have a data item to control whether it is expanded
The component should have a computed that gets the first 20 words of the first paragraph tag. You can use textContent to get text from an HTML node.
The computed is the most likely part to pose a challenge. It will look something like this
blurb() {
const div = document.createElement('div');
div.innerHTML = this.content; // this.content is the prop
const firstP = div.querySelector('p');
const text = firstP.textContent;
const match = text.match(/(\S+\s*){0,20}/);
return match[0];
}
Rough implementation, Pure Js approach
document.getElementById("addContent").onclick = display;
document.getElementById("ellipsAnchor").onclick = hideEllipsis;
function display() {
document.getElementById("instruction").classList+= " hide";
let content = document.getElementById("inputbox").value;
if(content.length > 30) {
let sliced = content.slice(30);
let unsliced = content.substring(0,29);
let spantag = document.createElement("span");
spantag.className = "toReplace hide"
let text = document.createTextNode(sliced);
spantag.appendChild(text);
let spantag1 = document.createElement("span");
let text1 = document.createTextNode(unsliced);
spantag1.appendChild(text1);
let contentTag =document.getElementById("content");
contentTag.appendChild(spantag1)
contentTag.appendChild(spantag)
document.getElementById("ellipsis").classList -= "hide";
}
}
function hideEllipsis(){
document.getElementById("ellipsis").classList += " hide";
document.querySelectorAll("span.hide")[0].classList -= " hide"
}
.hide {
display : none;
}
<textarea type="text" id="inputbox"></textarea>
<button id="addContent">
Show content
</button>
<div id="content">
</div>
<div class="hide" id="ellipsis">
Read More..
</div>
<div id="instruction">
Type more than 30 characters and click show content
</div>
You can write a vue directive to solve this.
Set max-height to the div.
count the words and append "Read more.." link to the content.
Add a click event to 'read more' to expand the DIV to full height.
For example see this codepen
let handler = ""
Vue.directive("viewmore", {
inserted: function (el, binding){
let maxlines = binding.value
let lineheight = parseFloat(getComputedStyle(el).lineHeight)
let paddingtop = parseFloat(getComputedStyle(el).paddingTop)
let lines = (el.clientHeight) / lineheight ;
let maxheight = (lineheight * maxlines) + paddingtop + (lineheight/5)
if(lines>maxlines){
el.classList.add('vmore')
el.style.maxHeight = maxheight + 'px'
el.addEventListener('click', handler = ()=> {
el.style.maxHeight = ""
el.scrollIntoView({behavior: "smooth"})
el.removeEventListener('click', handler)
el.classList.remove('vmore')
})
}
},
unbind: function (el, binding) {
el.removeEventListener('click', handler)
handler = ""
}
});
https://codepen.io/dagalti/pen/vPOZaB .
it works based on the lines in the content.
Code : https://gist.github.com/dagalti/c8fc86cb791a51fe24e5dc647507c4a3
Expanding on the answers by tom_h and Roy J, here's what I'm using in my vue application to make the ellipsis clickable:
Vue.component("ellipsis", {
template: "#ellipsis-template",
props: ['content'],
data: function() {
return {
wordLength: 3, // default number of words to truncate
showAll: false
}
}
});
<script type="text/x-template" id="ellipsis-template">
<span v-if="content.split(' ').length>wordLength && showAll">{{content}}
(less)
</span>
<span v-else-if="content.split(' ').length>wordLength && !showAll">
{{content.split(" ").slice(0,wordLength).join(" ")}}
...
</span>
<span v-else>{{content}}</span>
</script>
To call it:
<ellipsis :content="someData"></ellipsis>
I have a little problem.
I have a "use-button" which sets a cookie, telling that this deal is used. The button switch state to disabled.
The problem is that, if you go back and forwards, the button is no longer disabled and you can still use the button. If you refresh the page the button will be disabled.
This is my code:
export default Ember.Controller.extend(MobileHelper, {
goodie: Ember.computed.alias('model'),
tracker: Ember.inject.service(),
used: undefined,
fieldUsed: function(){
var pack_id = this.get('goodie.packContents.firstObject.id');
var cookies;
if (this.cookie.getCookie('gp_pv') === undefined) {
return false;
} else {
cookies = JSON.parse(this.cookie.getCookie('gp_pv'));
}
return cookies[String(pack_id)] === 1;
}.property('goodie.packContents.firstObject.id'),
profileComponent: function() {
return `goodie-${this.get('goodie.type')}-profile`;
}.property('goodie.type'),
actions: {
markSwiped: function() {
var cookies;
if (confirm("Er du sikker?")){
if (this.cookie.getCookie('gp_pv') === undefined){
cookies = {};
} else {
cookies = JSON.parse(this.cookie.getCookie('gp_pv'));
}
var pack_id = this.get('goodie.packContents.firstObject.id');
if (cookies[String(pack_id)] !== 1){
cookies[String(pack_id)] = 1;
}
jQuery("#itemUsable").hide();
jQuery("#itemUsed").show();
this.get('tracker').trackGoodieExternalLinkClick(this.get('goodie'));
this.cookie.setCookie('gp_pv', JSON.stringify(cookies), { expires: 50000, path: '/' });
this.container.lookup('view:toplevel').rerender();
route.transitionTo("index")
}
}
}
});
{{#if fieldUsed}}
<style>
#itemUsable {display:none;}
#itemUsed {display:block;}
</style>
{{else}}
<style>
#itemUsable {display:block;}
#itemUsed {display:none;}
</style>
{{/if}}
<div class="container" id="itemUsed">
<div class="goodie-page-swipe-action">
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<div class="btn gp-btn-primary btn-block btn-lg disabled">{{ t 'goodie.used'}}</div>
</div>
</div>
</div>
</div>
<div class="container" id="itemUsable">
<div class="goodie-page-swipe-action">
<div class="row">
<div class="col-xs-offset-3 col-xs-6">
<div {{ action 'markSwiped' }} class="btn gp-btn-primary btn-block btn-lg">{{ t 'goodie.use'}}</div>
</div>
</div>
</div>
</div>
Computed property values are normally cached and only updated when the values of the dependent keys change. From the provided code it is not immediately obvious that, when markSwiped gets run, goodie.packContents.firstObject.id immediately changes. I guess it doesn't, since fieldUsed and thus your templates do not get updated (which means the cookie value that you do update is never re-evaluated, until a full page refresh).
Things that come to mind that you could try:
You could try to make fieldUsed a volatile property which would hopefully cause it to be re-evaluated on every use (no longer cached)
fieldUsed: function() {...}.volatile()
Make fieldUsed depend on some other value instead, or in addition. For instance, as a crutch you could test some extraneous counter type variable that simply gets incremented somewhere in markSwiped
crutchCounter: 0,
fieldUsed: function() {}.property('goodie.packContents.firstObject.id', 'crutchCounter'),
markSwiped: function() {...; this.incrementProperty('crutchCounter'); ...}
If either of these work, you can then also forego the jQuery DOM manipulation you have going on in markSwiped.
I have eight cards with two parameters on each of them. The first parameter is year (2011, 2012, 2013, 2014), the second is category (studio, house, personal, commercial). It looks like this:
Studio 2011
House 2012
Commercial 2013
Personal 2012
Studio 2014
Commercial 2011
House 2014
Personal 2013
I need to sort them out, making needed cards bright, and not needed faded. By default all of them are bright. HTML:
<div class="card card-studio card-2011 card-bright">Studio 2011</div>
<div class="card card-house card-2012 card-bright">House 2012</div>
<div class="card card-commercial card-2013 card-bright">Commercial 2013</div>
<div class="card card-personal card-2012 card-bright">Personal 2012</div>
<div class="card card-studio card-2014 card-bright">Studio 2014</div>
<div class="card card-commercial card-2011 card-bright">Commercial 2011</div>
<div class="card card-house card-2014 card-bright">House 2014 </div>
<div class="card card-personal card-2013 card-bright">Personal 2013</div>
I add buttons with years:
2011
2012
2013
2014
When user clicks a year button, we take cards with needed year class and remove their "bright/faded" classes just in case. Then we add "bright" class to these cards. Then we take all the cards that do not have needed year and add them "faded" class (also removing previous classes just in case). And we also make the button underlined, and its siblings not underlined. It all looks like this:
$(".button").on("click", function(event){
event.preventDefault();
if($(this).hasClass("button-2011")){
$(".card-2011").removeClass("card-bright card-faded").addClass("card-bright");
$(".card").not(".card-2011").removeClass("card-bright card-faded").addClass("card-faded");
$(this).siblings().removeClass("button-active").end().addClass("button-active");
}
JSfiddle: https://jsfiddle.net/6vLzyowc/
Now it all seems simple, but trouble comes up when I try to add the second sorting condition, category.
On the one hand, I certainly need to do the same thing, as in the first case, i. e. make needed category bright, not needed faded:
if($(this).hasClass("button-house")){
$(".card-house").removeClass("card-bright card-faded").addClass("card-bright");
$(".card").not(".card-house").removeClass("card-bright card-faded").addClass("card-faded");
}
But it will make "bright" all cards with the needed category, and I also have the previous year sorting results. It seems that I need to sort them out first. So, I take the first year sorting results (i. e. "bright" cards) and make ones without needed category "faded":
if($(this).hasClass("button-house")){
$(".card-bright").not("card-house").addClass("card-faded");
}
It helps a little, but I still don't know how I can add the remaining cards with needed category now, so that both conditions are satisfied. So, how can I combine sorting previous sorting results and sorting all the items?
Fiddle with all the buttons: https://jsfiddle.net/hm1emr8p/
I think your approach is a bit over-complicated. The following works for any number of filters. The trick is to keep state of what is selected, and on any change just re-apply this state to the card-elements.
(function () {
var active = [];
$('.filter').each(function (idx, el) {
var $el = $(el);
active.push('');
$el.on('click', '.button', function () {
var $this = $(this);
active[idx] = $this.data('toggle');
$el.find('.button').removeClass('button-active');
$this.addClass('button-active');
update();
});
});
function update()
{
var a = active.join('');
if (a.length === 0) {
$('.card').removeClass('card-faded');
}
else {
$('.card').addClass('card-faded').filter(active.join('')).removeClass('card-faded');
}
}
})();
a {
text-decoration: none;
color: black;
}
.buttons {
margin-top: 40px;
}
.time {
margin-bottom: 20px;
}
.card-faded {
opacity: 0.3;
}
.button-active {
text-decoration: underline;
}
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<div class="card card-2011 card-studio">Studio 2011</div>
<div class="card card-2012 card-house">House 2012</div>
<div class="card card-2013 card-commercial">Commercial 2013</div>
<div class="card card-2012 card-personal">Personal 2012</div>
<div class="card card-2014 card-studio">Studio 2014</div>
<div class="card card-2011 card-commercial">Commercial 2011</div>
<div class="card card-2014 card-house">House 2014 </div>
<div class="card card-2013 card-personal">Personal 2013</div>
<div class="buttons">
<div class="filter time">
<a class="button button-2011" data-toggle=".card-2011">2011</a>
<a class="button button-2012" data-toggle=".card-2012">2012</a>
<a class="button button-2013" data-toggle=".card-2013">2013</a>
<a class="button button-2014" data-toggle=".card-2014">2014</a>
<a class="button button-all button-active" data-toggle="">All time</a>
</div>
<div class="filter category">
<a class="button button-studio" data-toggle=".card-studio">Studio</a>
<a class="button button-house" data-toggle=".card-house">House</a>
<a class="button button-commercial" data-toggle=".card-commercial">Commercial</a>
<a class="button button-personal" data-toggle=".card-personal">Personal</a>
<a class="button button-all button-active" data-toggle="">All</a>
</div>
</div>
I suggest saving the selected category and the selected timespan in variables:
var lastCategory = '';
var lastYear = '';
In your case you can use data- attributes to store information like this:
2011
and:
Studio
And perform actions based on the value of the data element in your click handler:
$(".category > .button").on("click", function(event){
event.preventDefault();
var tmpCat = $(this).data('cat');
lastCategory = tmpCat;
$(".card").removeClass("card-faded");
if (tmpCat == 'all') {
if (lastYear == 'alltime') {
$(".card").addClass("card-faded");
} else {
$(".card-"+lastYear).addClass("card-faded");
}
} else {
if (lastYear == 'alltime') {
$(".card-" + tmpCat).addClass("card-faded");
} else {
$(".card-" + tmpCat + ".card-"+lastYear).addClass("card-faded");
}
}
$(this).siblings()
.removeClass("button-active")
.end()
.addClass("button-active");
});
$(".time > .button").on("click", function(event){
event.preventDefault();
var tmpYear = $(this).data('year');
lastYear = tmpYear;
$(".card").removeClass("card-faded");
if (tmpYear == 'alltime') {
if (lastCategory == 'all') {
$(".card").addClass("card-faded");
} else {
$(".card-" + lastCategory).addClass("card-faded");
}
} else {
if (lastCategory == 'all') {
$(".card-" + tmpYear).addClass("card-faded");
} else {
$(".card-" + lastCategory + ".card-"+tmpYear).addClass("card-faded");
}
}
$(this).siblings()
.removeClass("button-active")
.end()
.addClass("button-active");
});
There a zillion ways that lead to rome, this is not ideal, but try to avoid if (1) then, if (2) then, if (3) then, etc.... code. I forked your fiddle and made a working demo.
In html I am having the following tags:
<span id=M26>2011-2012</span>
<div id=c26 STYLE="display:none">
<span id=M27>2012-2013</span>
<div id=c26 STYLE="display:none">
On Clicking on 2011-2012 or on 2012-2013 I want to set display property of div tag.
I am using the following Javascript code for this and I am calling the Javascript function in body tag. The output is showing style and display is not an object or property.
<script language="javascript">
function clickHnadler()
{
var xid= document.getElementsByTagName("span");
var xsp= xid[0].id;
alert("Span id is "+xsp);
if(xsp.charAt(0)=="M")
{
var oC = document.all("C"& xsp.substring(1,2));
if(oC.STYLE.display == "none")
{
oC.Style.Display = "";
}
else{
oC.Style.Display = "none";
}
}
}
</script>
use jquery:
you can pass in the function the element or the Id:
ex:
<span id=M26>2011-2012</span>
function clickHnadler(element)
{
var id = $(element > span).attr(id);
id[0] = 'c'; //not the nicest way, maybe use a replace or something like that
$(id).show(); //or $(id).css('display','list');
}
You may use clickHandler has following way,
function clickHandler(e) {
window.document.links[0].handleEvent(e);
}
You need to bind event spacifically to elements you want to handle click for. for more information please refer following link,
http://docs.oracle.com/cd/E19957-01/816-6409-10/evnt.htm#1009606
Based on what i understand from your question, I come up with this.
<script type="text/javascript" src="jquery1.8.js"></script>
<span id=M26>2011-2012</span>
<div id=c26 STYLE="display:none">
2011-2012 details</div>
<br />
<span id=M27>2012-2013</span>
<div id=c26 STYLE="display:none">
2012-2013 details
</div>