Приложение прогноз погоды на JS с выбором города

5 (100%) 10 vote[s]

В дополнение к другим вариантам использования OpenWeatherMap API добавим ещё один вариант с конкретной функцией выбора города. Приложение прогноз погоды на 5 дней будет работать с использованием вызова API по названию города api.openweathermap.org/data/2.5/forecast?q= {enjcity name}, {код страны}.



Приложение прогноз погоды: что будем делать

Для начала сделаем приложение монофункциональным, то есть только прогноз погоды на 4 дня (вполне достаточно) с возможностью выбора любого города либо из выпадающего списка, либо путем ввода в поле input.

Сразу обозначим проблемы:

  1. Использование ввода названия города/населенного пункта влечет несколько проблем, основные из которых — это необходимость ввода названия на английском (что пользователь не всегда сможет корректно сделать) и наличие в мире нескольких населенных пунктов с одинаковым названием, но в разных координатах. Чтобы избежать такой путаницы, в документации рекомендуется вызывать API по идентификатору города, чтобы получить однозначный результат. Но имеющиеся файлы (http://bulk.openweathermap.org/sample/) огромные, и работать с ними нецелесообразно. Для небольших виджетов-проектиков этим, скорее всего, не занимаются.
  2. Использование списка ограничено тем самым списком, который разработчик посчитает нужным предложить для выбора пользователю. Обычно этот вариант используют для показа погоды для одного-нескольких конкретных городов.

Из этого вытекают 2 основные задачи:

  • для поля input реализовать проверку ошибок запроса, если пользователь ввёл неверное название (точнее, несуществующее) и сервер возвратил 404.
  • для списка — просто вручную его создать.



Кроме того, для AJAX запроса будут использоваться данные, полученные из поля input (будет только 1 запрос) , поэтому на тот случай, если пользователь воспользуется выпадающим списком, необходимо связать поле input и select (вставить данные из select в input).

Чтобы стало понятнее, чего мы хотим добиться, покажем, как это работает в конечном результате:

Вот здесь можно попробовать:

Введите название города

(на английском)

Например: Kharkiv, Kyiv, Tokio,
Moscow, Saint Petersburg, Tver ...

Или выберите из списка (25 городов)

Прогноз на 4 дня





Опция выбора из списка больше похожа на подсказку для пользователя (если он по каким-либо причинам испытывает трудности с вводом английского названия). В видимой части мы прописываем название на кириллице, а в значении value пишем валидное название на английском. Именно его вставляем в поле input и уже оттуда берем его для отправки в запросе.

Вот исходный код:

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
</head>
<body>
<div class="left">
<h2>Введите название города</h2>
 <p>Например: <span style="color:green; font-size:16px;">Kharkiv, Kyiv, Tokio, <br>Moscow, Saint Petersburg, Tver ...</span></p>
 <input type="text" id="city" placeholder="Введите название города (англ.)..."/>
 <button id="cityB" onclick="showDateTime();">OK</button>
<h2>Или выберите из списка (25 городов)</h2>
<p><select  class="select" name="cityes">  
    <option selected value="Kyiv">Киев</option>
	<option value="Kharkiv">Харьков</option>
	<option value="Lviv">Львов</option>	
    <option value="Moscow">Москва</option>
	<option value="Tver">Тверь</option>
	<option value="Stavropol">Ставрополь</option>
	<option value="Chelyabinsk">Челябинск</option>	
    <option value="Minsk">Минск</option>
	<option value="Astana">Астана</option>
    <option value="Tbilisi">Тбилиси</option>
	<option value="Chisinau">Кишинёв</option>	
	<option value="Berlin">Берлин</option>
    <option value="Rio de Janeiro">Рио-де-Жанейро</option>
    <option value="Beijing">Пекин</option>
    <option value="Melbourne">Мельбурн</option>
    <option value="Managua">Манагуа</option>	
	<option value="Madrid">Мадрид</option>
    <option value="Helsinki">Хельсинки</option>
    <option value="Warsaw">Варшава</option>
    <option value="Ottawa">Оттава</option>
    <option value="London">Лондон</option>
	<option value="Rome">Рим</option>
	<option value="Mexico city">Мехико</option>
    <option value="Delhi">Дели</option>    
    <option value="Tokio">Токио</option>	
   </select></p>  
<p class="text-center">Прогноз на 4 дня <br><span id="cityC"></span><p>	
	<div id="tablo">            
		<div class="day">
			  <img id="tmp4"/><br>
			 <p id="day1"></p>
			 <div id="demo7"></div>
			 <div id="demo5" ></div>
			 <div id="demo6"></div>
			 <div id="demo4"></div>
		</div>

		<div class="day">
			   <img id="tmp5"/><br>
			 <p id="day2"></p>
			 <div id="demo10"></div>
			 <div id="demo11"></div>
			 <div id="demo8"></div>
			 <div id="demo9"></div>
		</div> 

		<div class="day">
			   <img id="tmp6"/><br>
			 <p id="day3"></p>
			 <div id="demo14"></div>
			 <div id="demo15"></div>
			 <div id="demo12"></div>
			 <div id="demo13"></div>
		</div> 

		<div class="day">
			   <img id="tmp7"/><br>
			 <p id="day4"></p>
			 <div id="demo18"></div>
			 <div id="demo19"></div>
			 <div id="demo16"></div>
			 <div id="demo17"></div>
		</div>
  </div>
  </div>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="js2.js"></script>
</body>
</html>

CSS:

.day {
	 border-radius:4px;
	 box-shadow: 0 0 10px rgba(0,0,0,0.5);
	 text-align:center;
	 padding:20px;
	 margin-bottom: 20px;
	 width:auto;
 }
  .day, #demo {
     background-image: linear-gradient(141deg, #9fb8ad 0%, #6600FF 45%, #6600CC 75%);
} 
#tablo {
    display: none;
    flex-flow: row wrap;
    align-items: stretch;
    justify-content: space-around;
 }
#day1, #day2, #day3, #day4 {
	font-size:1.8em;
	color:#e8ffe8;
	text-shadow: 0 0 5px #070707;
 } 
#demo7, #demo6, #demo10, #demo8, #demo14, #demo12, #demo18, #demo16 {
    font-size:.8em;
	color:#FF66FF;
 }
#demo5, #demo11, #demo15, #demo19 {
    font-size:1.3em;
	color:#0768f0;
 }
#demo4, #demo9, #demo13, #demo17 {
    font-size:1.3em;
	color:#ff00bb;
 }
 input{
  font-size:16px;
  font-weight:bold;
  font-style:italic;
  border: solid 2px #68bcff;
  outline: 0;
  width: 30%;
  height: 36px;
  border-top-left-radius: 50px;
  border-bottom-left-radius: 50px;
  padding-left: 40px;
  margin: 10px 0px;
  z-index: 9999999;
}
#cityB {
  height: 40px;
  width: 40px;
  border: solid 2px #68bcff;
  border-left: none;
  outline: 0;
  border-top-right-radius: 50px;
  border-bottom-right-radius: 50px;
  padding-right: 5px;
  margin-left:-6px;
  background: #68bcff;
  color: #fff;
}
.text-center {
  background: radial-gradient(circle at 65% 15%, #6699FF, #660066);
  font-family:Open Sans;
  font-size:32px;
  color: #ffffff;
  text-shadow: 1px 1px 2px black, 0 0 1em #ffffff;
  text-align:center;
 }
#cityC {
 color:#8df2a1;
 font-size:26px;
 padding:10px;
}
.select { 
  display: block; 
  font-size: 16px; 
  font-family: sans-serif; 
  font-weight: 700; 
  color: #444; 
  line-height: 1.3; 
  padding: .6em 1.4em .5em .8em; width: 100%; 
  max-width: 200px; 
  box-sizing: border-box; 
  margin: 0; 
  border: 1px solid #aaa;
  box-shadow: 0 1px 0 1px rgba(0,0,0,.04); 
  border-radius: .5em;
 -moz-appearance: none;
 -webkit-appearance: none;
  appearance: none;
  background-color: #fff; 
  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'), linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%); 
  background-repeat: no-repeat, repeat;
  background-position: right .7em top 50%, 0 0;
  background-size: .65em auto, 100%; 
} 
 .select::-ms-expand { display: none; } 
 .select:hover { border-color: #888; } 
 .select:focus { border-color: #aaa; 
  box-shadow: 0 0 1px 3px rgba(59, 153, 252, .7);
  box-shadow: 0 0 0 3px -moz-mac-focusring; 
  color: #222;
  outline: none; 
} 
 .select option { font-weight:normal; } 
 *[dir="rtl"] .select, :root:lang(ar) .select, :root:lang(iw) .select { 
  background-position: left .7em top 50%, 0 0; 
  padding: .6em .8em .5em 1.4em; 
}
@media all and (max-width: 900px){
	input{
  font-size:12px;
  font-weight:normal;
  font-style:italic;
  border: solid 1px #68bcff;
  outline: 0;
  width: 70%;
  height: 30px;
}
button{
  height: 30px;
  width: 30px;
  border: solid 1px #68bcff;
}
}

JavaScript/jQuery:

$(function(){
  let input = $('#city'),
      inpVal = input.val();
  
  $('.select').on('change', function(){
    input.val(inpVal + $(this).val());
  });
});

$('#cityB').on('click', function(){
	$('#tablo').css('display','flex');
			
	var city=$('#city').val();
  //Здесь вам нужно вставить свой бесплатный ключ
	var apiURI2 = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=здесь вам нужно вставить свой код`;
		console.log("success getWeather2");
		console.log(apiURI2);
		$.ajax({
		  url: apiURI2,
		  dataType: "jsonp",
		  type: "GET",
		  async: "true",		 
	      timeout : 500,
                    success : function(data) {
                        console.log("Success");
                    },
                    error : function(e) {
                        console.log("Error");
						$('#cityC').html('<p style="color:red";>ERROR</p><p style="color:#bef7f1";>Проверьте корректность названия</p>');
						$('#tablo').css('display','none');
                    },
                    done : function(e) {
                        console.log("DONE");
                    },  
		}).done(dataHandler3);
	
$('#cityC').text('в' + ' ' + city);	 
function dataHandler3(data) {
	dataString = JSON.stringify(data);
	var now = new Date();
	let h = now.getHours();
    var num = 8-(Math.floor(h/3));
//завтра
		document.getElementById("demo6").innerHTML = data.list[num+3].dt_txt;
		document.getElementById("demo4").innerHTML = "Макс." + " " + Math.floor((data.list[num+3].main["temp"])-273,15)+"°C";
		document.getElementById("demo7").innerHTML = data.list[num].dt_txt;
		document.getElementById("demo5").innerHTML = "Мин." +  " " + Math.floor((data.list[num].main["temp"])-273,15)+"°C";
		var imgURL = "https://openweathermap.org/img/w/" + data.list[num+3].weather[0].icon + ".png";
		$("#tmp4").attr("src", imgURL);
//послезавтра
		document.getElementById("demo8").innerHTML = data.list[num+11].dt_txt;
		document.getElementById("demo9").innerHTML = "Макс. " + " " + Math.floor((data.list[num+11].main["temp"])-273,15)+"°C";
		document.getElementById("demo10").innerHTML = data.list[num+8].dt_txt;
		document.getElementById("demo11").innerHTML = "Мин." +  " " + Math.floor((data.list[num+9].main["temp"])-273,15)+"°C";
		var imgURL = "https://openweathermap.org/img/w/" + data.list[num+11].weather[0].icon + ".png";
		$("#tmp5").attr("src", imgURL);
//после-послезавтра
		document.getElementById("demo12").innerHTML = data.list[num+19].dt_txt;
		document.getElementById("demo13").innerHTML = "Макс." + " " + Math.floor((data.list[num+19].main["temp"])-273,15)+"°C";
		document.getElementById("demo14").innerHTML = data.list[num+16].dt_txt;
		document.getElementById("demo15").innerHTML = "Мин." +  " " + Math.floor((data.list[num+17].main["temp"])-273,15)+"°C";
        var imgURL = "https://openweathermap.org/img/w/" + data.list[num+19].weather[0].icon + ".png";
		$("#tmp6").attr("src", imgURL);		
//после-после-послезавтра	
		document.getElementById("demo16").innerHTML = data.list[num+27].dt_txt;
		document.getElementById("demo17").innerHTML = "Макс." + " " + Math.floor((data.list[num+27].main["temp"])-273,15)+"°C";
		document.getElementById("demo18").innerHTML = data.list[num+24].dt_txt;
		document.getElementById("demo19").innerHTML = "Мин." +  " " + Math.floor((data.list[num+25].main["temp"])-273,15)+"°C";
        var imgURL = "https://openweathermap.org/img/w/" + data.list[num+27].weather[0].icon + ".png";
		$("#tmp7").attr("src", imgURL);		
				
   }
 
   });
 //Показ дни недели 
  function showDateTime() {
				
				
				var d = new Date();
				var n1, n2, n3, n4, n5;
				var weekday = new Array(7);
					weekday[0] = "Воскресенье";
					weekday[1] = "Понедельник";
					weekday[2] = "Вторник";
					weekday[3] = "Среда";
					weekday[4] = "Четверг";
					weekday[5] = "Пятница";
					weekday[6] = "Суббота";
					 
					 if(d.getDay() >= 3){
						   n1 = weekday[(d.getDay()+1)];
						   n2 = weekday[(d.getDay()+2)];
						   n3 = weekday[(d.getDay()+3)];
						   n4 = weekday[7-(d.getDay()+4)];} if(d.getDay() >= 4)
					     {
						   n1 = weekday[(d.getDay()+1)];
						   n2 = weekday[(d.getDay()+2)];
						   n3 = weekday[7-(d.getDay()+3)];
						   n4 = weekday[9-(d.getDay()+4)];} if(d.getDay() >= 5)
					     {
						   n1 = weekday[(d.getDay()+1)];
						   n2 = weekday[7-(d.getDay()+2)];
						   n3 = weekday[9-(d.getDay()+3)];
						   n4 = weekday[11-(d.getDay()+4)];} if(d.getDay() >= 6)
					     {
						   n1 = weekday[7-(d.getDay()+1)];
						   n2 = weekday[9-(d.getDay()+2)];
						   n3 = weekday[11-(d.getDay()+3)];
						   n4 = weekday[13-(d.getDay()+4)];}  if(d.getDay() < 3) 
						 {
					       n1 = weekday[(d.getDay()+1)];
						   n2 = weekday[(d.getDay()+2)];
						   n3 = weekday[(d.getDay()+3)];
						   n4 = weekday[(d.getDay()+4)];
						  
					     }
						
								document.getElementById("day1").innerHTML = n1;
								document.getElementById("day2").innerHTML = n2;
								document.getElementById("day3").innerHTML = n3;
								document.getElementById("day4").innerHTML = n4;								
			}
				showDateTime(); 

По поводу ключа. Используйте свой, поскольку у бесплатного ключа есть ограничения по количеству запросов (в минуту не более 60 — чего вполне достаточно). Не хотелось бы на него много навешивать. Тем более что процедура его получения — дело 1 минуты. Всё просто, «как двери»: достаточно зарегистрироваться (перейдите по ссылке) и получить код.

Добавляем функциональность

Теперь, когда все работает, можно исправить ошибки и расширить функциональность приложения. Например, поставим задачу показать на карте выбранный город. Для этого будем использовать карты Open Street Map и Leaflet API. Получать координаты выбранного города будем при помощи API Nominatim.

Также добавим запрос текущей погоды (current weather data) и местоположение по IP.

Для множественного выбора вариант с select — options, который мы сделали вначале, не подойдет, поэтому перейдем на списки ul-li такого формата:

<li class="first"><span value="Черновицкая область" class="k">Черновицкая обл.</span>
 <ul id="select2">Города
   <li> <span class = "s" value = "Vashkivtsi, Chernivets’ka Oblast’, UA">Вашковцы</span></li>
   <li> <span class = "s" value = "Vyzhnytsia, UA">Вижница</span></li>
   <li> <span class = "s" value = "Hertsa, UA">Герца</span></li>
   <li> <span class = "s" value = "Zastavna, UA">Заставна</span></li>
   <li> <span class = "s" value = "Kitsman, UA">Кицмань</span></li>
   <li> <span class = "s" value = "Novodnistrovsk, UA">Новоднестровск</span></li>
   <li> <span class = "s" value = "Novoselytsya, UA">Новоселица</span></li>
   <li> <span class = "s" value = "Sokyriany, Chernivets’ka Oblast’, UA">Сокиряны</span></li>
   <li> <span class = "s" value = "Storozhynets, UA">Сторожинец</span></li>
   <li> <span class = "s" value = "Khotyn, UA">Хотин</span></li>
   <li> <span class = "s" value = "Chernivtsi, UA">Черновцы</span></li>
  </ul>
</li>	

Забегая наперед скажем, что обратное геокодирование и показ геолокации на карте не всегда завершается sucsess, так как населенных пунктов c одинаковым названием может быть несколько. В этом случае сервер возвращает пустой файл. Поэтому можно использовать фрейм, который вставляется в страницу при получении пустого ответа (для этого проверяем при помощи инструкции if(d.length == 0){console.log(‘ERROR’);}else{}; длину полученного json файла), в котором есть поле для выбора нужного населенного пункта из списка найденных для отображения на карте.

Можно избежать такой необходимости, поскольку есть возможность делать уточнённый запрос (мы использовали запрос с таким синтаксисом: https://nominatim.openstreetmap.org/search/${city}?format=json&addressdetails=1&limit=1&polygon_svg=1,

а уточнённый выглядит так: https://nominatim.openstreetmap.org/search/${country}+${oblast}+${city}?format=json&addressdetails=1&limit=1&polygon_svg=1.)

Но поскольку мы используем одни и те же данные, взятые из поля input для запроса погоды и координат, тогда карта отображаться будет точно, а погода — часто отвечает 404. Поэтому от уточненного запроса мы отказались, так как пришлось бы пересмотреть всю логику работы приложения. Атрибут value в списках как раз будет использоваться для более точного запроса и снижения к минимуму вариабельности результата поиска. Вдобавок ко всему иногда есть отличия в используемом синтаксисе, по которому можно найти город в https://nominatim.openstreetmap.org/search/ и для него же погоду на https://openweathermap.org/find. И напоследок — варианты переименованных городов, с какими на разных сервисах путаница. Поэтому чтобы привести всё в соответствие пришлось поломать голову. Вот, что получилось:

При реализации карт пришлось решать проблему: после первого запроса при каждом последующем выборе карта не обновляется. Решили вопрос таким способом: контейнер для карт вставлять каждый раз заново по клику кнопки ОК :

$('#map2').replaceWith('<div id="map2" style="height:500px;width:80%;"></div>');

Также не нашлось вариантов с переводом описания погоды (типа оvercast cloud), что пришлось решать таким способом: на сайте скопировали все варианты описания (Weather condition codes), создали из них массив, затем перевели всё это (что,тоже проблематично в плане правильной интерпретации Shower sleet, heavy intensity shower rain и т.д.) и создаём массив такой же длины. Затем сравниваем полученный ответ описания с первым массивом, получаем его код и вставляем в страницу уже свой перевод из второго массива с таким же кодом:

var znak = ['overcast clouds','clear sky','...'];
var znakRU = ['Пасмурно','Чистое небо','...'];

//затем после получения данных.....
var m = znak.indexOf(data.weather[0].description);
document.getElementById("#id").innerHTML = "Погода: " + " " + znakRU[m];

В остальном — чисто технические детали, связанные с реализацией отображения страницы и всех её интерактивных элементов. Приложение прогноз погоды построено с использованием многих блоков из уже имеющихся (в примерах по ссылке в начале статьи, а также в статье, посвященной бесплатным API карт).

Приложение прогноз погоды на JS с выбором города скачать

Size: 346 kB
Published: 05.02.2020

Почасовое отображение прогноза погоды

Если попробовть извлечь максимум пользы от получаемого json, то можно реализовать почасовый вывод погоды для нашего приложения.

Со структурой json мы разобрались: в нем 40 строк (ключей) list, в каждой из которых значение представляет объект. В нем — также по несколько строк, значение которых также может быть в виде объекта.

Получить эти данные несложно, но проблема оказалась в другом: в периодичности обновления файла на сервере openweather. В предыдущем примере наше приложение получает данные через фиксированный промежуток — из каждой восьмой строки. Мы взяли время 12.00 и 03.00. Хотя изначально при наблюдении за работой приложения в течение определенного времени заметили, что выводимые данные то совпадают с заданными критериями поиска (выводит именно в 12 и 3 часа), то иногда «сползают» на 1 строку вперёд (выводит 6 и 15 часов). Казалось бы, наш алгоритм работы с массивом правильный, тогда почему данные «плавают»? Наше «приложение прогноз погоды» для одного дня валидность от этого не потеряет, но если выводить погоду для каждых 3-х часов, такое положение вещей не годится.

Логика получения значения нужной нам строки простая: получаем часы текущего времени:

var d = new Date();
d.getHours();

затем получаем текущий 3-х часовый промежуток:

var K = Math.floor((d.getHours()/3))

и получаем количество промежутков до начала и конца каждых следующих суток:

var LAST = (14-(K));
var FIRST = (7-(K));

Проблему, почему плавают часы, решилась также просто: сравнив файлы, которые получаем, например, в 15:10 и 17:10 обнаруживается, что в первом случае мы получаем json, в котором ещё есть строка с погодой на 15:00 текущего дня, а в 17:10 уже получаем файл с первой строкой 18:00. Понятно, что наш алгоритм сползет на 1 строку и для следующего дня с 17:10 до 18:00 будет получать данные уже для 18: 00. А после 18:00 снова начнет корректно отображать прогноз, а ближе к 21:00 снова сползет на 1 час.

Таким образом, нам нужно просто сравнивать дату из первой строки json с текущей датой, и если текущая меньше, то от коэффициента отнимать 1.

var g = new Date(data.list[0].dt_txt);
if(d < g){ K = Math.floor((d.getHours()/3))}else{K = Math.floor((d.getHours()/3)-1)};

Этот нюанс будет полезен тем, кто впервые решил попробовать поработать с openveather API и столкнулся с такой же проблемкой.

Кроме того, чтобы получить минимальную и максимальную температуру, уже недостаточно выводить температуру в 3 часа ночи (как минимальную) и в 12 дня (как максимальную), как мы поступили в предыдущем варианте. Днём может быть холоднее, чем ночью! Чтобы не гадать, просто создадим массив из показателей каждого дня из 8 элементов и из них выберем наибольшее и наименьшее значение:

min1 = arr1[0];
  max = mi1;
  for (i = 1; i < arr.length; ++i) {
      if (arr[i] > max) max = arr[i];
      if (arr1[i] < min) min = arr[i];
  }

В итоге получили финальное приложение с почасовым прогнозом погоды, с конвертером валют (как же без него), возможностью выбора города и показом его на карте, в пустое место в блоке текущего дня мы вставили рандомный канвас (можно помедитировать):

Скачать полный вариант можно в форме ниже:
(в коде вставлены ключевые комментарии)

Приложение почасовый прогноз погоды с выбором города, показом на карте и конвертером валют

Финальный вариант приложения с почасовым прогнозом погоды, с конвертером валют, возможностью выбора города и показом его на карте.

Убедительная просьба использовать свой API Key!


Size: 473 kB
Published: 28.04.2020

Только прогноз погоды

Вариант полной версии приложения с одним только почасовым прогнозом, без конвертера (кроме удаления конвертера, добавлен красивый фон и применен эффект css mix-blend-mode к тексту):

Приложение почасовый прогноз погоды с выбором города с показом на карте (v1.2)

Читайте больше по теме:

Подписаться
Уведомление о
guest
2 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Макс
Макс
24.09.2020 17:15

Ух ты круто) очень пригодилось)) а то не успели в учаке нам Js дать как делайте выборку данных с открытого API
Жаль нету видео с объяснением как это работает((

Просмотры: 1526

Популярные записи