Detect change in hash value of URL using JavaScript - javascript

I'm working on a simple application which is single page based (due to project restrictions) and has dynamic content. I understand the dynamic content alright but what I don't understand is how to set-up a script that changes the html of a div when the hash value in the URL changes.
I need a JavaScript script to work as such:
Url: http://foo.com/foo.html div contents: <h1>Hello World</h1>
Url: http://foo.com/foo.html#foo div contents: <h1>Foo</h1>
How would this work?
Please help! Thanks.

You can listen to the hashchange event:
$(window).on('hashchange',function(){
$('h1').text(location.hash.slice(1));
});

personally, I'd use sammy which gives you the flexibility to template the hashtag (add placeholders and be able to read them back). e.g.
<script src="/path/to/jquery.js"></script>
<script src="/path/to/sammy.js"></script>
<script>
$(function(){
// Use sammy to detect hash changes
$.sammy(function(){
// bind to #:page where :page can be some value
// we're expecting and can be retrieved with this.params
this.get('#:page',function(){
// load some page using ajax in to an simple container
$('#container').load('/partial/'+this.params['page']+'.html');
});
}).run();
});
</script>
Load foo.html
Load bar.html
An example can be found here: http://jsfiddle.net/KZknm/1/

Suppose we have list of items, each items has a hash tag as #id
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}" alt="${limitRecipeTitle(recipe.title)}">
</figure>
<div class="results__data">
<h4 class="results__name">${recipe.title}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
Now when a user click on any of those list item or reload (http://localhost:8080/#47746) an item with hash tag, hash event will be fired. To recive the fired hash event we must register hash event listener in our app.js
//jquery:
['hashchange', 'load'].forEach(event => $(window).on(event, controlRecipe));
//js:
['hashchange', 'load'].forEach(event => window.addEventListener(event, controlRecipe));
catch the id in your controlRecipe function
const controlRecipe = async ()=>{
//jq
const id = $(window)..location.hash.replace('#','');
//js
const id = window.location.hash.replace('#','');
if(id){
//console.log(id);
state.recipe = new Recipe(id);
try {
await state.recipe.getRecipe();
state.recipe.calcTime();
state.recipe.calcServings();
console.log(state.recipe);
} catch (error) {
alert(error);
}
}
}

Related

java script document.getElementById() is not working

hope you having great days,
i'm trying to get div for change its attribute value
but document.getElementById() is not working
i put the statement for after the div is completly load,
and even tried windows.load but nothing workout,
any suggestions?
var size_list = [[76.01, 77.81,23.99,11.09,0,11.09]
,[69.9, 71.56,20.51,14.22,9.59,14.22]
,[64.1,65.63,17.56,17.19,18.34,17.19]
,[59.22,60.63,15.15,19.69,25.64,19.69]
,[54.79,56.09,12.87,21.95,32.34,21.95]]
function size_update(src,index) {
console.log("i'm working");
let element = window.getComputedStyle(document.getElementById("thisid"),null)
element.setProperty('.height',size_list[index][0]+'%');
element.setProperty('width',size_list[index][1]+'%');
element.setProperty('top',size_list[index][2]+'%');
element.setProperty('right',size_list[index][3]+'%');
element.setProperty('bottom',size_list[index][4]+'%');
element.setProperty('left',size_list[index][5]+'%');
}
const Videoframe = ({src,index,title,before_span,span,after_span,subtitle}) =>{
try{
// do something
return(
<div>
<script>
function (){console.log("start!")};
</script>
<div className="videoframe" name = "thisid" id = "thisid" >
<div className="textgroup1">
<div className="title">{title}</div>
<div className="main">
<span>{before_span}</span>
<i className="better">{span}</i>
<span> {after_span}</span>
</div>
<div className="padding" />
<div className="subtitle">
{subtitle}
</div>
</div>
</div>
<script>
function (){console.log("ended!")};
</script>
</div>
);}
finally{
size_update(src,index);
}
}
export default Videoframe;
export { size_list }
tried googling and window.load
window.getComputedStyle() returns the style of the object, but you need the DOM-Element.
To get an element once the document is loaded you need to use the window load event by doing either
window.addEventListener("load", () => {
let element = document.getElementById("thisID");
});
or
window.onload = () => {
let element = document.getElementById("thisID");
};
In both cases, you supply a function to be executed once the document is loaded. Note that the code inside the eventhandler is executed only when the site is loaded, so after the code which is written after this code snippet. If you want to access the element, you'll need to write the logic for the element inside the function.

How would I implement event listeners instead of inline html event handlers in this Javascript example?

I was trying to get data from an API that gives me a list of movies and this is how I implemented the rendering of the results.
const getAllData = async () => {
const movieData = await getMovies()
const movieContainer = movieData.map((movie)=> {
const listItem = `
<li class="movie">
<img src="${movie.Poster}"></img>
<h1>
${movie.Title}
</h1>
<h2>Release Year: ${movie.Year}</h2>
<p>
<button onclick="getDetails('${movie.imdbID}')">
Click for more details
</button>
</p>
</li>
`;
return listItem
}).join('')
document.getElementById("movieList").innerHTML = movieContainer;
}
Here is the getDetails function that the button click calls.
function getDetails(id){
sessionStorage.setItem('movieId', id);
window.location = 'movie.html';
return false;}
This code works as intended. However, what I was having trouble with is that I want to implement this logic with event listeners instead of inline HTML event-handlers. How could I implement this?
Would I need to change anything drastic about my current code?
Create a <li> element instead of an HTML string, then select the button descendant using querySelector and you can use addEventListener:
const getAllData = async () => {
const movieData = await getMovies();
const ul = document.getElementById("movieList");
for (const movie of movieData) {
const li = ul.appendChild(document.createElement('li'));
li.className = 'movie';
li.innerHTML = `
<img src="${movie.Poster}"></img>
<h1>
${movie.Title}
</h1>
<h2>Release Year: ${movie.Year}</h2>
<p>
<button>
Click for more details
</button>
</p>
`;
li.querySelector('button').addEventListener('click', () => {
getDetails(movie.imdbID);
});
}
};
I'd also highly recommend against direct concatenation of external input into an HTML string, like with
<img src="${movie.Poster}"></img>
unless the input is absolutely trustworthy, since that can result in arbitrary code execution, which is a security risk. If you aren't doing it already, I'd suggest either verifying that the interpolated values are well formatted (for example, without anything which would result in HTML markup, like <script> tags), or assign the dynamic properties/attributes after the <li> has been populated with the base template.

Refresh MEN stack webpage without refreshing the entire page

I am creating a data analytics dashboard using the MEN stack. (express,node,ejs) However, I would like to display the real-time data that refreshes every 5 seconds WITHOUT refreshing the HTML page which I am doing so currently using the following scripts.
Everything works. But, it's kind of an eyesore to see the page refreshing all the time. Is there a way to use AJAX or something to refresh a particular DIV or PAGE?? I've tried different AJAX functions on different places but it doesn't work. Maybe am doing it the wrong way. Any advice?
async showTasksMain(req, res) {
const queryWeather = "SELECT * FROM c ORDER BY c.EventProcessedUtcTime desc"
const items = await this.taskDao.findweather(queryWeather); //Return variables
const tempdata = await this.taskDao.findtempdata(queryWeather);
res.render("index",{items:items, tempdata:tempdata});
}
async findweather(queryWeather) {
debug("Querying for items from the database");
if (!this.container) {
throw new Error("Collection is not initialized.");
}
const { result: results } = await this.container.items
.query(queryWeather)
.toArray();
var obj = results[0];
return obj;
}
app.get("/", (req, res, next) => taskList.showTasksMain(req, res).catch(next));
<script type = "text/JavaScript">
function AutoRefresh( t ) {
setTimeout("location.reload(true);", t);
}
</script>
<body onload="JavaScript:AutoRefresh(30000)">;
Here is a sample javascript code to refresh partial page like some div
Suppose , you have menu like beloe:
ul id="menu">
<li>About Us
<li>Services
<li>Products
</ul>
and you have content div like below:
<div id="content"></div>
you can then write a code something like:
$("#menu li a").click(function(e) {
// prevent from going to the page
e.preventDefault();
// get the href
var href = $(this).attr("href");
$("#content").load(href, function() {
// do something after content has been loaded
});
});
You can refer Load
Load data from the server and place the returned HTML into the matched element.
Hope it helps.

Executing a <script> tag on append after the DOM has loaded

I'm trying to watch videos onClick in a modal without having to visit the video pages themselves.
I'm using Vue.js and through a series of Ajax calls I am able to get most of the way there.
Codpen: https://codepen.io/nolaandy/pen/BrbBzO
If you click "vs Suns" at top, you get a listing of all video posts. Then clicking any of the images, the modal component pops up and takes in the title of the post dynamically.
I want to run a video in there as well so I try to run this script tag:
< script class="_nbaVideoPlayerScout" data-team="warriors" data-videoId="/video/{{unique videoId from post ajax call}}" data-width="768" data-height="732" src="https://www.nba.com/scout/team/cvp/videoPlayerScout.js"></script>
When the modal pops up, I see the correct title of the post/image I clicked on, and I see the script tag exactly as it should be in the inspector, but the script tag never runs.
Is there some different way I should be injecting this script than this? (This is inside the axios response call)
let theVideoId = response.data.content[0].videoID
let s = document.createElement('script')
s.setAttribute('class', '_nbaVideoPlayerScout')
s.setAttribute('data-team', 'warriors')
s.setAttribute('data-videoId', '/video/' + theVideoId)
s.setAttribute('data-width', '768')
s.setAttribute('data-height', '732')
s.setAttribute('src', 'https://www.nba.com/scout/team/cvp/videoPlayerScout.js')
document.getElementById('popupVideo').appendChild(s);
MODAL COMPONENT -- Fired on the click of one of the post thumbnails
const videoModal = Vue.component('VideoModal', {
props: {
id: {
type: String,
required: true
}
},
data: function () {
return {
post: [],
}
},
mounted() {
const singleApi = 'https://cors-anywhere.herokuapp.com/www.nba.com/warriors/api/1.1/json?textformat=html&nid='
axios.get(singleApi + this.id).then((response) => {
this.post = response.data.content[0]
console.log('THE RESPONSE', response)
let theVideoId = response.data.content[0].videoID
let s = document.createElement('script')
s.setAttribute('class', '_nbaVideoPlayerScout')
s.setAttribute('data-team', 'warriors')
s.setAttribute('data-videoId', '/video/' + theVideoId)
s.setAttribute('data-width', '768')
s.setAttribute('data-height', '732')
s.setAttribute('src', 'https://www.nba.com/scout/team/cvp/videoPlayerScout.js')
document.getElementById('popupVideo').appendChild(s);
}).catch((error) => {
console.log(error)
})
},
methods: {
goBack: function () {
router.go(-1)
}
},
template:`<div>
<div id="video-popup">
<button class="close-video-popup" #click="goBack">close me</button>
<div class="video-popup-wrapper">
<div class="video-popup--title">{{post.title}}</div>
<div class="video-popup--video" id="popupVideo"></div>
<div class="video-popup--share"></div>
</div>
</div>
</div>`
})
On a whim I made this change:
// document.getElementById('scriptMe').appendChild(s);
document.body.appendChild(s);
and boom, script runs and video loads.
Which is super interesting, because "why", right?
Edit:
In addition, trying other script injection methods discussed here.
document.write method
document.write(s.outerHTML) // s is a script node
also works. In fact, you can embed that script node in a div and it works as well.
createContextualFragment method
// var $container = document.getElementById('scriptMe'); // does not work
var $container = document.body
var range = document.createRange()
$container.appendChild(range.createContextualFragment(script_str))
works, where script_str is an html string literal. This will work both as "<script>....</script>" or "<div id="myDiv"><script>...</script></div>"
but all the methods I tested ultimately needed injection to be done in body.
codepen

Can't pass data from Sammy to knockout template

I have been looking at this for quite few hours and I don't think I am able to see the solution.
This is my router.js:
define('router', ['jquery', 'config', 'nav','store'], function ($, config, nav, store) {
var
concepTouch = Sammy('body', function () {
// This says to sammy to use the title plugin
this.use(Sammy.Title);
this.use(Sammy.Mustache);
// Sets the global title prefix
this.setTitle(config.title.prefix);
// So I can access sammy inside private methods
var sammy = this;
function establishRoutes() {
// Defines the main container for content then
var mainConainer = $(config.mainContentContainerId);
// Adds animation loading class to the main container
mainConainer.addClass(config.loadingAnimationCssClass);
// iterates through routes defined in config class then
_.forEach(config.appRoutes, function(obj, key) {
// defines each one as a route
sammy.get(obj.hashV, function(context) {
// Store the requested route as the last viewed route
store.save(config.stateKeys.lastView, context.path);
// Fetches its html template
context.render(obj.tmpltURL, { 'routeData': context.params })
// Appends that htmlo template to the main container and removes loading animation
.then(function(content) {
mainConainer.removeClass(config.loadingAnimationCssClass).html(content);
});
// Finally adds the route title to the prefix
this.title(obj.title);
});
// Overriding sammy's 404
sammy.notFound = function () {
// toast an error about the missing command
toastr.error(sammy.getLocation() + ' Does not exist yet!');
// Go to last visited anf if not
sammy.setLocation(
store.fetch(config.stateKeys.lastView) || config.getDefaultRoute()
);
};
});
}
// Calls for routes to be established
establishRoutes();
}),
// runs concep touch as a sammy App with the initial view of default route
init = function () {
// Try to get today's last visit and if not available then fallback on default
concepTouch.run(store.fetch(config.stateKeys.lastView) || config.getDefaultRoute());
// Make the correct nav item active and add Click handlers for navigation menu
nav.setStartupActiveClass(store.fetch(config.stateKeys.lastView) || sammy.getLocation())
.addActiveClassEventHandlers();
};
return {
init: init,
concepTouch: concepTouch
};
});
This when I submit the search form gets this template for me:
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
searchresults.get(to Some how Get routeData.term);
});
</script>
and I can not find the right way to make Mustache pass the data from this line of router.js context.render(obj.tmpltURL, { 'routeData': context.params }) to the {{routeData.term}} inside the template.
{{routeData}} on its own returns `SAMMY.OBJECT: {"TERM": MY SEARCH TERM}`
which I can't navigate to the property i want to from it using . notation. Furthermore even if that worked it can not be passed into Javascript which is what I really need as
searchresults.init(); is waiting for this paramter `searchresults.init(routeData.term);`
Or maybe the answer is to find a way to access sammy's context here? outside of sammy in order to get the params? something like Sammy.Application.context.params['term'] but ofcourse application has no such method so don't know!? :(
Am I going totally the wrong way about it? How Can I easily pass the query string params as accessible objects inside my template so knockout can use it.
Your help is greatly appreciated.
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{#routeData}}{{term}}{{/routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
var searchTerm = "{{#routeData}}{{term}}{{/routeData}}";
searchresults.get(searchTerm);
});
</script>

Categories