QuerySelector Id Target when Matches Value in Object - javascript

I cannot figure out how to correctly target and display an HTML id tag when a corresponding matching JS object, contained within an array, has a "live" value of true.
I want a JS loop to display a link in schedule module only if that link id matches the "scheduleId" of an object and when that object's "live" value is true. How would I target and display this link?
See my HTML here:
<div class="test-schedule">
<h2>Schedule</h2>
<div><span>School of Ed</span><a class="link-class" id="ed">School of Ed link</a></div>
<div><span>School of Humanities</span><a class="link-class" id="hss">School of Humanities link</a></div>
<div><span>School of Science</span><a class="link-class" id="sci">School of Science link</a></div>
<div><span>School of Nursing</span><a class="link-class" id="nursing">School of Nursing link</a></div>
</div>
<style>
.link-class{display:none}
</style>
JS here:
const eventList = [
{
scheduleId: 'ed',
live: 'true',
},
{
scheduleId: 'hss',
live: 'false',
},
{
scheduleId: 'sci',
live: 'false',
},
{
scheduleId: 'nursing',
live: 'false',
},
];
Codepen link:
https://codepen.io/lvl12sealclubber/pen/PoWbJZQ?editors=1011

If I understand correctly you want all the links to be hidden and only display some based on a JS value? Could you just do:
.link-class {
visibility: hidden;
}
for (let event of eventList) {
if (event.live) {
document.getElementById(event.scheduleId).style.visibility="visible"
}
}

If I understand you want to show the link of that event based on if the event is live on the events object, you can try something simple like creating a class that will show the element like this
const eventList = [
{
scheduleId: "ed",
live: "true",
},
{
scheduleId: "hss",
live: "false",
},
{
scheduleId: "sci",
live: "true",
},
{
scheduleId: "nursing",
live: "false",
},
];
const checkLinks = () => {
for (let event of eventList) {
if (event.live === "true") {
document.querySelector("#" + event.scheduleId).classList.add("show-link");
console.log(event.scheduleId);
}
}
};
checkLinks();
.link-class {
display: none;
}
.show-link {
display: inline-block;
color: blue;
}
<div class="test-schedule">
<h2>Schedule</h2>
<div>
<span>School of Ed </span
><a class="link-class" id="ed">School of Ed link</a>
</div>
<div>
<span>School of Humanities </span
><a class="link-class" id="hss">School of Humanities link</a>
</div>
<div>
<span>School of Science </span
><a class="link-class" id="sci">School of Science link</a>
</div>
<div>
<span>School of Nursing </span
><a class="link-class" id="nursing">School of Nursing link</a>
</div>
</div>

I’d update data type for ‘live’ property to a boolean instead of a string.
In a real world, this will be data driven and you wouldn’t have to compare dates but just check the ‘live’ property if it’s true or not.
If I understand correctly, when a user lands on this page, you calculate the current date/time and show which event is live. If this is the case, you can just compare current date/time and show link for the one that is live. There is no need to update event.live. In fact, you shouldn't update your data through JS.
Also, instead of updating style for banner through JS, just maintain a placeholder on the top and control styles through CSS. I didn’t see a value to do it through JS.

You could filter out the unwanted array elements and then use a simple forEach to display. Something like this:
const eventList = [{
scheduleId: 'ed',
live: 'true',
}, {
scheduleId: 'hss',
live: 'false',
}, {
scheduleId: 'sci',
live: 'false',
}, {
scheduleId: 'nursing',
live: 'true',
}];
const scheduleEl = document.querySelector("#schedule")
eventList
.filter(event => event.live === 'true')
.forEach(event => {
scheduleEl.innerHTML += `<p>- ${event.scheduleId}</p>`;
});
<p>Live event schedule:</p>
<div id="schedule">
</div>

Related

How to Store Data Property Value from a Specific Item in Rendered List in Vue

I'm trying create a follow button on list items in Vue. My strategy is to grab the value of a particular list item property and store it in the data object. Then use this value in a method to add it to an array in my database.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock">+follow</button>
</div>
I'm not sure how to get the value of result.symbol "into" the button element to set the value symbol in the data object below.
<script>
export default {
data() {
return {
results: [ // this is populated by an api call
{
currency: "USD"
exchangeShortName: "NYSE"
name: "International Game Technology PLC"
stockExchange: "NYSE"
symbol: "IGT"
},
{...},
...
],
symbol: "",
};
},
followStock() {
// add this.symbol to database array
},
},
};
</script>
I'm guessing there might be an easier strategy I'm overlooking as I'm still new to Vue, so any other solution that essentially allows me to fire off the value of result.symbol from any rendered result to my database would be awesome.
You can just pass the result as a parameter to your method.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result)">+follow</button>
</div>
And in your method:
methods: {
followStock(result) {
// do something with result
console.log({result});
let symbol = result.symbol;
},
}
P.S I didn't see you put your followStock() inside a methods object, but I did so in the example. https://v2.vuejs.org/v2/api/#methods
Write directly as a function call.
The vue compiler will turn followStock(result.symbol) into function(event) {followStock(result.symbol)}.
new Vue({
el: '#app',
data() {
return {
results: [
{
name: "International Game Technology PLC",
symbol: "IGT"
},
{
name: "A name",
symbol: "A symbol"
}
]
};
},
methods: {
followStock(symbol) {
console.log(symbol)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
</div>
As Nazaire mentioned you can access the results anywhere inside the child elements when using v-for.
(it works like a normal for-loop)
It's not only limited to the corresponding element (the element in which you do v-for)
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
followStock(symbol){
// you can now add symbol to db
}

How to link words in an HTML string and assign them #click method in VueJS 2?

I have a array of strings in which I want to linkify certain words like "User object", "Promise", etc like this:
var strings = ['This returns a promise containing a User Object that has the id', 'next string']
This needs to be rendered like this
<div class="wrapper">
<div class="item" v-for="str in strings" v-html="str"></div>
</div>
The problem is I want to replace words like "User object", "Promise" and bind them to a #click event that my app can handle.
So if it were rendered like I want it to be, it would be something like this (the same v-for loop above rendered manually)
<div class="wrapper">
<div class="item">This returns a promise containing a User object that has the id</div>
<div class="item">next string</div>
</div>
I tried doing this but it doesn't bind the #click event
methods: {
linkify(str) {
return str.replace(/user object/, 'User object');
}
}
Any ideas?
Here's an example of a component that takes in a string for the full message and a string for the text to replace with a link and renders a span with that message with the link text wrapped in a <a> tag:
Vue.component('linkify', {
template: '#linkify-template',
props: {
value: { type: String },
linkText: { type: String }
},
computed: {
before() {
return this.value.split(this.linkText)[0];
},
after() {
return this.value.split(this.linkText)[1];
}
}
});
new Vue({
el: '#app',
data() {
return {
message: 'This returns a promise containing a User Object that has the id',
}
},
methods: {
foo() {
console.log('clicked')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script type="text/x-template" id="linkify-template">
<span>
{{ before }}
<a href="#" #click.prevent="$emit('click')">
<code>{{ linkText }}</code>
</a>
{{ after }}
</span>
</script>
<div id="app">
<linkify link-text="User Object" :value="message" #click="foo"></linkify>
</div>
Okay figured it out. If somebody has a better way to do it please answer too!
Vue.component('linkify', {
props: ['value', 'words'],
template: `<span :is="html"></span>`,
data() {
return {
html: Vue.compile('<span>' + this.value.replace(new RegExp('(' + this.words.join('|') + ')', 'g'), `<code>$1</code>`) + '</span>'),
}
}
});
Now all I need to do in the main app is this:
<div class="wrapper">
<div class="item" v-for="str in strings">
<linkify :value="str" :words="['user object', 'promise']" #click="help"></linkify>
</div>
</div>
Unfortunately this only works with full version of Vue (which has the compile function)

Toggle class on click in VueJS

I am learning Vuejs and I am constantly finding simple things like removing a class to be a pain. Please tell me how I can allow the .active class to be added or removed based on clicks between the 3 links.
In the example below the adding is working fine but it adds .active to all the links, and does not remove when clicked on again.
<div id="app">
<h2>App</h2>
<ul>
<li>Link text</li>
<li>Link text</li>
<li>Link text</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink() {
// the line below did not work
// document.getElementsByClassName("active").isActive = false,
this.isActive = true
}
}
})
JSfiddle is here, https://jsfiddle.net/s9r4q0gc/
You need to catch the event handler in the method and using that you can refer to the callee i.e. anchor object in this case.
See the fiddle : https://jsfiddle.net/s9r4q0gc/2/
activeLink(event) {
if(event.target.className == "noclass")
{
event.target.className = "link active";
}
else
{
event.target.className = "noclass";
}
}
UPDATED:
May be try this fiddle and see if it is hitting the bulls eye : https://jsfiddle.net/s9r4q0gc/4/
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink(event) {
var checkboxes = document.getElementsByClassName ("noclass");
for (var i=0; i<checkboxes.length; i++) {
checkboxes[i].className = "link active";
//checkboxes[i].className = "link";
}
event.target.className = "noclass";
}
}
})
What you can do is use a data property to hold the currently active link. Then with that you'll be able to have any given link test if it is the active link and if the .active class should be applied.
Then it's just a matter of setting that property's new value when a link is clicked. If the same link that's currently active is clicked, then the property is cleared, thus removing the .active class from all links. Otherwise, the class is added to the link that was clicked.
This is a CodePen demonstrating what I mean in action, but your markup could look something like this:
<li><a href="#" class="link" :class="{ active: activeId == 'link-1' }"
#click.prevent="activeLink('link-1')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-2' }"
#click.prevent="activeLink('link-2')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-3' }"
#click.prevent="activeLink('link-3')">Link text</a></li>
and your JS could look something like this:
data: {
activeId: null
},
methods: {
activeLink(linkIdent) {
this.activeId = this.activeId === linkIdent ? null : linkIdent
}
}
It's obviously not as clean a solution as it could be, but I am sticking to your requirement that the solution fit the markup you provided.
Here is an alternative solution that may modify your code more than you want. I thought this might be useful since you are just learning and may be interested in an alternative. I started by declaring your links as an array of objects in vue so that we can assign an active attribute to each of them. Then we can just toggle the active value inline or use the toggleActive function (not currently in use.. just there for illustration if you prefer to use functions over inline logic).
Html:
<div id="app">
<h2>App</h2>
<ul>
<li v-for="l in links">
{{l.text}}
</li>
</ul>
</div>
Javascript:
var app = new Vue({
el: '#app',
data: {
links: [{
text: "Link text",
active: false
},{
text: "Second text",
active: false
},{
text: "Third text",
active: false
}]
},
methods: {
//To use this function make #click in html to:
//#click="toggleActive(l)"
toggleActive(x) {
x.active = !x.active;
}
}
})
https://jsfiddle.net/yrbt90v9/
Use V-for and an array of items so you have no need to statically type the links. This allows for the dynamic functionality you are looking for.
var app = new Vue({
el: '#app',
data: {
links: [
{text: "Link Text", active: false},
{text: "Link Text", active: false},
{text: "Link Text", active: false}
]
},
methods: {
activate(link) {
link.active = !link.active
}
}
})
.link{
text-decoration: none;
color: #555;
}
.active{
text-decoration: underline;
color: #42b983;
}
<div id="app">
<h2>App</h2>
<ul>
<li v-for="link in links">Link text</li>
</ul>
</div>

Weird bug with ng-click

So I've used ng-repeat to create a list of all my songs in an album (refer to this question I asked earlier)
So what I am trying to do now is make it so when a user clicks an item from the list, it plays the refered track. This is my app:
enitoniApp.controller('musicItems', ['$scope', function ($scope) {
$scope.ngplaySong = function (ref, name) {
playSong(ref, name)
}
$scope.albums = [
{
name: 'Over The Mountains',
price: 0,
tracks: [
{
name: 'Over The Mountains',
ref: 'otm',
released: 0,
},
{
name: '!C3',
ref: 'ice',
released: 0,
},
{
name: 'Dark Clouds',
ref: 'clouds',
released: 0
},
{
name: 'Fog',
ref: 'fog',
released: 0
}
]
},
{
name: 'test-album',
price: 5000,
tracks: [
{
name: 'test',
ref: 'null'
},
]
}
]
}]);
As you can see, I'm trying to call a regular function using ng-click. This regular function (playSong()) is inside the code for my player, and it plays a track based on the reference id.
snippet from player.js:
/** Play single song **/
function playSong(ref, name) {
showPlayer();
clearPlaylist()
playlistPosition = 0;
addToPlaylist(ref, name, 'play')
}
So I have this in my html:
<li ng-repeat="album in albums">
<div class="info">
<p>{{album.name}}</p>
<p>{{album.price | currency}}</p>
</div>
<ul>
<li ng-animate="grid-fade" ng-repeat="track in album.tracks">
<div class="grid-item" ng-click="ngplaySong('{{track.ref}}','{{track.name}}')">
<div class="cover">
<img ng-src="/img/covers/art_{{track.ref}}.png" alt="">
</div>
<div class="info">
<p>{{track.name}}</p>
<p>{{track.released}}</p>
</div>
</div>
</li>
</ul>
</li>
The weird thing is that even though this is rendering correctly:
THIS gets outputted into the console even though the parameters are correct:
Why is it not binding the data when the function gets called, am I missing something here?
I do not think that you need those braces inside your ng-click. Try this:
<div class="grid-item" ng-click="ngplaySong(track.ref, track.name)">
The thing is that you pass an expression to ng-click which is then parsed by Angular and it is smart enough to recognize the variables from current scope. You can read more on Angular expressions here: https://docs.angularjs.org/guide/expression
In fact, there is a very nice and easy example in Angular ng-click documentation which includes accessing a local variable inside the ng-click expression: https://docs.angularjs.org/api/ng/directive/ngClick

Update Knockout `attr` binding from DOM instead of from ViewModel

I am using Knockout.js to populate a set of HTML5 <details> elements. Here is the structure:
<div class="items" data-bind="foreach: Playlists">
<details class="playlist-details" data-bind="attr: {id: 'playlist-details-' + $index(), open: isOpen}">
<summary>
<span data-bind="text: name"></span> - <span data-bind="text: count"></span> item(s)
<div class="pull-right">
<button data-bind="click: $parent.play, css: {disabled: count() == 0}, attr: {title: playbtn_title}" class="btn"><i class="icon-play"></i> Play</button>
<button data-bind="click: $parent.deleteList" class="btn btn-danger"><i class="icon-trash"></i> Delete</button>
</div>
</summary>
<div class="list" data-bind="with: items" style="padding-top: 2px;">
...
</div>
</details>
</div>
The data in the ViewModel looks something like this:
var VM = {
Playlists: [
{
name: "My Playlist1",
count: 3,
items: [<LIST OF SONG ID'S>],
playbtn_title: "Play this playlist",
isOpen: true
},
{
name: "My Playlist2",
count: 5,
items: [<LIST OF SONG ID'S>],
playbtn_title: "Play this playlist",
isOpen: null
},
{
name: "My Playlist3",
count: 0,
items: [],
playbtn_title: "You need to add items to this list before you can play it!",
isOpen: null
}
]
};
I have added the ability to remember the open or closed state of the details view using the isOpen property of the ViewModel and an attr binding (As originally described here).
However, when I click the <summary> to expand the details, the ViewModel does not get updated - unlike value bindings, attr bindings aren't two-way.
How can I get this binding to update when the attribute value changes?
I know that the browser triggers a DOMSubtreeModified event when the element is opened or closed, but I;m not sure what I would put there - several things I have tried (including .notifySubscribers(), if (list.open()) ..., etc.) cause looping, where the property being changed makes the event trigger again, which changes the property again, which triggers the event again, etc.
Using $ to play DOM directly is not ko way :-)
Just create a two-way binding for HTML5 details tag, it's cheap in ko.
http://jsfiddle.net/gznf3/
ko.bindingHandlers.disclose = {
init: function(element, valueAccessor) {
if (element.tagName.toLowerCase() !== 'details') {
throw "\"disclose\" binding only works on <details> tag!";
}
var value = valueAccessor();
if (ko.isObservable(value)) {
$(element).on("DOMSubtreeModified", function() {
value($(element).prop('open'));
});
}
},
update: function(element, valueAccessor) {
$(element).prop('open', ko.unwrap(valueAccessor()));
}
};
The way that I found in the end that works is simply to have the DOMSubtreeModified "manually" update the value:
$(document).on('DOMSubtreeModified', 'details.playlist-details', function(e) {
var list = ko.dataFor(this);
list.open(this.getAttribute('open'));
});
(Somehow, this does not cause the looping that more-complex constructs I tried had caused.)

Categories

Resources