I'm quite new to JavaScript, and I'm stuck with an issue which I didn't manage to find an answer for.
I have an array, each item of which is tied to a card in markup.
When a button is pressed, a form pops up and allows to enter name and url for the new image, which is then unshifted to thebeginning of the array.
The last element of the array is popped, so there's always the same number of elements.
And the issue itself is:
When I add a single card, it works perfect: last card is popped and the new one is tucked in the beginning.
But when I use and submit the form again, the card that I have previously edited changes as well, although it should not.
I suspect that the mistake lies in the function that updates the cards, but I'm all out of ideas how to fix it.
Is there a way to fix that function?
const formAdd = document.querySelector('.popup__form-add')
const newElement = { name: '', link: '' };
const blank = { name: '', link: '' };
const placeName = document.querySelector('.popup__edit_type_place-name');
const placeImage = document.querySelector('.popup__edit_type_place-picture');
const elements = document.querySelector('.elements');
const cards = elements.querySelectorAll('.element');
const buttonAdd = document.querySelector('.profile__button-add');
const buttonAddClose = document.querySelector('.popup__add-button-close');
//content popup functions
function popupAddDisplay() {
popupAdd.classList.add('popup_opened');
}
function popupAddHide() {
popupAdd.classList.remove('popup_opened');
}
function contentUpdate() {
event.preventDefault();
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
initialCards.unshift(blank);
blank.name = newElement.name;
blank.link = newElement.link;
console.log(initialCards);
cardsCheck();
popupAddHide();
};
//cards array, page content loads from here
const initialCards = [{
name: 'Архыз',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/arkhyz.jpg'
},
{
name: 'Челябинская область',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/chelyabinsk-oblast.jpg'
},
{
name: 'Иваново',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/ivanovo.jpg'
},
{
name: 'Камчатка',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/kamchatka.jpg'
},
{
name: 'Холмогорский район',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/kholmogorsky-rayon.jpg'
},
{
name: 'Байкал',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/baikal.jpg'
}
];
//compares info from the markup with the array, then pushes array info into markup
function cardsCheck() {
cards.forEach(function(item, index) {
const nameAlt = initialCards[index].name;
const imageSrcAlt = initialCards[index].link;
item.querySelector('.element__name').textContent = nameAlt;
item.querySelector('.element__image').src = imageSrcAlt;
if (nameAlt == '') {
item.style.display = 'none'
}
console.log(nameAlt);
});
}
document.onload = cardsCheck();
buttonAdd.addEventListener('click', () => {
popupAddDisplay()
});
buttonAddClose.addEventListener('click', popupAddHide);
formAdd.addEventListener('submit', contentUpdate);```
You always use the same blank object. So at the end you have the same object multiple times in the array. If you change an attribute of this object it is changed wherever it is in the list also.
To avoid this you need to create a new object before adding it to the array
function contentUpdate() {
event.preventDefault();
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
let blank = { name: '', link: '' };
initialCards.unshift(blank);
blank.name = newElement.name;
blank.link = newElement.link;
console.log(initialCards);
cardsCheck();
popupAddHide();
};
Every time you create a new card, it seems like you override the global variables blank and newElement.
However, you don't have to. You could create a local variable in your contentUpdate function called newElement.
That way, each time updateContent is called, a new local variable is created.
You could therefore try the following code:
const formAdd = document.querySelector('.popup__form-add')
const placeName = document.querySelector('.popup__edit_type_place-name');
const placeImage = document.querySelector('.popup__edit_type_place-picture');
const elements = document.querySelector('.elements');
const cards = elements.querySelectorAll('.element');
const buttonAdd = document.querySelector('.profile__button-add');
const buttonAddClose = document.querySelector('.popup__add-button-close');
//content popup functions
function popupAddDisplay() {
popupAdd.classList.add('popup_opened');
}
function popupAddHide() {
popupAdd.classList.remove('popup_opened');
}
function contentUpdate() {
event.preventDefault();
const newElement = {}
newElement.name = placeName.value;
newElement.link = placeImage.value;
console.log(newElement);
initialCards.pop();
initialCards.unshift(newElement);
console.log(initialCards);
cardsCheck();
popupAddHide();
}
Related
Can you tell me how using only Javascript we can add elements to a html document? There is a function createCards() that creates cards, after that the forEach method fills these cards with content from some array. The last step remains, add to the document. I know that the prepend method is applicable here (and I don't like innerhtml... But I don't quite understand how to designate the element that I need to insert. Thanks a lot!
const initialCards = [
{
name: 'place1',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/arkhyz.jpg'
},
{
name: 'place2',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/chelyabinsk-oblast.jpg'
},
{
name: 'place3',
link: 'https://pictures.s3.yandex.net/frontend-developer/cards-compressed/ivanovo.jpg'
},
];
const editProfile = document.querySelector('.profile__edit-button');
const popupProfile = document.querySelector('.popup__profile');
const popupNewPlace = document.querySelector('.popup-new-place');
const newPlaceButton = document.querySelector('.profile__add-button');
const closeButtons = document.querySelectorAll('.popup__close');
const yourName = document.querySelector('.profile__info-name');
const nameInput = popupProfile.querySelector('#profile-name');
const yourJob = document.querySelector('.profile__info-subtitle');
const jobInput = popupProfile.querySelector('#profile-job');
const formProfile = popupProfile.querySelector('.popup__edit-profile');
const formNewPlace = popupNewPlace.querySelector('.popup__edit-profile');
const contentItems = document.querySelector('.content');
function createCard (item) {
const contentItemTemplate = document.getElementById('content-item').content;
const newContentItem = contentItemTemplate.querySelector('.content__item').cloneNode(true);
newContentItem.querySelector('.content__title').textContent = item.name;
newContentItem.querySelector('.content__item-image').alt = item.name;
newContentItem.querySelector('.content__item-image').src = item.link;
newContentItem.querySelector('.content__like').addEventListener('click', function(event){
event.target.classList.toggle('content__like_active');
})
newContentItem.querySelector('.content__trash').addEventListener('click',function(event){
(event.target.closest('.content__item')).remove();
});
return newContentItem;
};
initialCards.forEach(createCard);
You could use appendChild() method, "document.querySelector('.containerForExample').appendChild('newContentItem)
you should read these articles to get more familiar with how to create an element and add it to the DOM using javascript
create Element function
append child function
insert before function
I hope that solves your problem. Thanks
I have a coding challenge, which am sure am getting it right , but the challenge requires that certain steps need to be in account before moving forward. And i seem to be skipping a step which is to de-structure a value from a target of an event(hope am saying it right)
The question is to get the expected event parameter to the target property with a de-structure.
Is my code wrong?
const displaySelectedUser = (event) => {
var newcal = event.target.value;
console.log(newcal);
console.log((event.target.value));
var user = getSelectedUser(newcal);
var properties = Object.keys(user);
console.log(properties);
properties.forEach(prop => {
const span = document.querySelector(`span[data-${prop}-value]`);
if (span) {
span.innerText = user[prop];
}
});
};
It's not wrong - but the only practical opportunity you have for destructuring is here:
var newcal = event.target.value;
Do this:
var { target: { value: newcal } } = event;
Because you're not using the other properties of event, you could move this up to the function declaration instead:
const displaySelectedUser = ({ target: { value: newcal } }) => {...}
Now newcal will already be defined as event.target.value in your function.
Here is the pseudo-code in question: https://jsfiddle.net/yzps2gef/40/
I'm trying to understand why I cannot access an object's properties directly in one scenario (see ISSUE #1 in comments) but I can in another scenario (see ISSUE #2 in comments). I'm failing to see the difference between the two. Thanks!
Here's the fiddle code:
window.DataStore = function () {
var url = new Url(),
filters = new Filters(),
orderBy,
orderByDir,
setOrderBy = function (x, y) {
orderBy = x;
orderByDir = y;
},
getOrderBy = function () {
return orderBy;
},
getOrderByDir = function () {
return orderByDir;
};
return {
url: url,
filters: filters,
orderBy: orderBy,
orderByDir: orderByDir,
setOrderBy: setOrderBy,
getOrderBy: getOrderBy,
getOrderByDir: getOrderByDir
};
};
window.Url = function () {
var get = function (ds) {
var url = 'xyz.php';
console.log(ds);
// ISSUE #1: These do not work. It results in: xyz.php?orderby=undefined&orderbydir=undefined.
// Why can't I access them directly like I do below with the dataStore.filters.someFilterOption?
url = url + '?orderby=' + ds.orderBy;
url = url + '&orderbydir=' + ds.orderByDir;
// These work when I use the "get" functions.
// url = url + '?orderby=' + ds.getOrderBy();
// url = url + '&orderbydir=' + ds.getOrderByDir();
return url;
}
return {
get: get
};
};
window.Filters = function () {
var someFilterOption = 0;
return {
someFilterOption: someFilterOption
};
};
window.Grid = function () {
var dataStore = new DataStore(),
doSearch = function () {
console.log(dataStore.url.get(dataStore));
},
render = function () {
doSearch();
// ISSUE #2: Why can I access this one directly but not the order bys?
if (dataStore.filters.someFilterOption) {
console.log('Why was I able to read this one (dataStore.filters.someFilterOption) directly and not have to have a getSomeFilterOption() function to read it? But when it comes to the orderBy and orderByDir above I cannot read them directly.');
}
}
return {
dataStore: dataStore,
render: render
};
};
window.MyReUsableGrid = function () {
var grid = new Grid(),
showSomeFilterOption = function () {
grid.dataStore.filters.someFilterOption = 1;
},
render = function () {
grid.render();
};
grid.dataStore.setOrderBy(4, 'asc');
return {
showSomeFilterOption: showSomeFilterOption,
render: render
};
};
// The Screen
var myGridScreen = new MyReUsableGrid();
myGridScreen.showSomeFilterOption();
myGridScreen.render();
Because when your object gets returned from the function this line gets evaluated:
orderBy: orderBy,
And as the variable orderBy isnt set yet it is actually:
orderBy: undefined
Now later you call setOrderBy and set the internal variable orderBy to a value which you can expose through the getter, but that doesnt get reflected to the objects property.
IMO the whole thing should be restructured so that the methods work with their context:
window.DataStore = () => ({
url: new Url(),
filters: new Filters(),
applyOrder(order, dir) {
this.orderBy = order;
this.orderByDir = dir;
},
});
That way you dont need getters at all.
I'm trying to write an extension with just using JavaScript. I wrote it with Python through Hello World! code. But, yet in the beginning, my button for menu items is not working. Also, I couldn't add menu item with Hello World! code. I think, I miss something.
The button code is here:
const Lang = imports.lang;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;
const TimeButton = new Lang.Class({
Name: "Salah Time",
Extends: PanelMenu.Button,
_init: function () {
let box = new St.BoxLayout({
style_class: "system-status-icon"
});
let label = new St.Label({text: "Salah Time"});
box.add_child(label);
this.actor.addActor(box);
}
});
function init() {
}
function enable() {
let but = new TimeButton();
Main.panel._leftBox.insert_child_at_index(but, 1);
}
function disable() {
Main.panel._leftBox.remove_child(but);
}
There is no many tutorial for GJS. I'm already trying to write by reading other extensions.
Thanks.
const Lang = imports.lang;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;
const TimeButton = new Lang.Class({
Name: "TimeButton",
Extends: PanelMenu.Button,
_init: function () {
this.parent(null, "TimeButton");
// Icon
this.icon = new St.Icon({
icon_name: "appointment-symbolic",
style_class: "system-status-icon"
});
this.actor.add_actor(this.icon);
// Menu
this.menuItem = new PopupMenu.PopupMenuItem("Salah Time", {});
this.menu.addMenuItem(this.menuItem);
}
});
function init() {
}
function enable() {
let indicator = new TimeButton();
Main.panel.addToStatusArea("should-be-a-unique-string", indicator);
// hide
Main.panel.statusArea["should-be-a-unique-string"].actor.visible = false;
// change icon
Main.panel.statusArea["should-be-a-unique-string"].icon.icon_name = "appointment-soon-symbolic";
// show
Main.panel.statusArea["should-be-a-unique-string"].actor.visible = true;
}
function disable() {
// you could also track "indicator" and just call indicator.destroy()
Main.panel.statusArea["should-be-a-unique-string"].destroy();
}
Hope that helps someone (if you aren't around anymore).
I've been working a project that allows a user to manage Option Types and Options. Basically user can add a new Option Type, let's say they name it Color and then they add the options - Black, Red, Purple, etc. When the collection first loads up the existing records, an empty option should be added at the end
When a user starts typing in the text field, I want to add a new empty option , thereby always giving the user a new field to work with.
I have this almost working, but can't figure how to properly add new empty option to a new Option Type or to existing option types. The push method keeps crashing Plunkr. Any input is appreciated, short sample review of the plunkr is below
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.optionTypeId = 0;
$scope.productOptionId = 0;
$scope.productEditorModel = {
"ProductOptions": [0],
"OptionTypes": [0]
};
$scope.productEditorModel.optionTypeName = '';
$scope.addEmptyOption = function (optionTypeId) {
var emptyOption = { ProductOptionId: 3, ProductId: '1066', OptionTypeId: 1, OptionValue: '', Price: '', IsStocked: true };
console.log(emptyOption);
//$scope.productEditorModel.ProductOptions.push(emptyOption);
};
$scope.loadData = function () {
$scope.productEditorModel.OptionTypes = [{ OptionTypeId: 1, OptionName: 'Color' },{ OptionTypeId: 2, OptionName: 'Size' },];
$scope.productEditorModel.ProductOptions = [{ ProductOptionId: 1, ProductId: '1066', OptionTypeId: 2, OptionValue: 'Medium', Price: '', IsStocked: true, },{ ProductOptionId: 2, ProductId: '1066', OptionTypeId: 1, OptionValue: 'Black', Price: '', IsStocked: true }];
angular.forEach($scope.productEditorModel.ProductOptions, function (item) {
//console.log(item.OptionTypeId);
$scope.addEmptyOption(item.OptionTypeId);
});
};
$scope.loadData();
$scope.removeOption = function (option) {
var index = $scope.productEditorModel.ProductOptions.indexOf(option);
$scope.productEditorModel.ProductOptions.splice(index, 1);
};
$scope.filterEmptyElements = function (optionTypeId) {
$scope.emptyElements = $.grep($scope.productEditorModel.ProductOptions, function (k) { return k.OptionValue === "" || angular.isUndefined(k.OptionValue) && k.OptionTypeId == optionTypeId });
};
$scope.update = function (option, index) {
var optionTypeId = option.OptionTypeId;
$scope.filterEmptyElements(optionTypeId);
if (!angular.isUndefined(option.OptionValue) && $scope.emptyElements.length == 1 && option.OptionValue.length > 0) {
$scope.addOption(optionTypeId);
} else if (angular.isUndefined(option.OptionValue)) {
$scope.removeOption(option);
}
};
$scope.addOptionType = function () {
var optionTypeId = --$scope.optionTypeId;
var optionName = $scope.productEditorModel.optionTypeName;
var newOptionType = { OptionTypeId: optionTypeId, OptionName: optionName };
$scope.productEditorModel.OptionTypes.push(newOptionType);
$scope.addEmptyOption(optionTypeId);
};
$scope.editOptionType = function (optionType) {
$scope.editing = true;
};
$scope.saveOptionType = function (optionType) {
$scope.editing = false;
};
$scope.trackOptionTypesCount = function () {
if ($scope.productEditorModel.OptionTypes.length == 3) {
$scope.isMaxOptionTypes = true;
} else {
$scope.isMaxOptionTypes = false;
}
};
$scope.removeOptionType = function (optionType) {
var index = $scope.productEditorModel.OptionTypes.indexOf(optionType);
$scope.productEditorModel.OptionTypes.splice(index, 1);
$scope.trackOptionTypesCount();
};
});
See the plunker below:
http://plnkr.co/edit/YHLtSwQWVb2swhNVTQzU?p=info
The error you get that $ is not defined is because you haven't included jQuery. You don't need jQuery for this though, array.map should be able to perform the same functionality.
$scope.emptyElements = $scope.productEditorModel.ProductOptions.map(function (k) {
return k.OptionValue === "" || angular.isUndefined(k.OptionValue) && k.OptionTypeId == optionTypeId
});
And it crashes because inside $scope.loadData you have
angular.forEach($scope.productEditorModel.ProductOptions, function (item) {
$scope.addEmptyOption(item.OptionTypeId);
});
and then inside $scope.addEmptyOption you try to
$scope.productEditorModel.ProductOptions.push(emptyOption);
So the foreach will loop for each item in $scope.productEditorModel.ProductOptions, which you keep adding options to so....? Infinite loop.
Non-crashing version: http://plnkr.co/edit/5Sc2sWfhKBs9kLCk83f1?p=preview
What you really should do though is look over your data structure. Make the ProductOptions a sub-object of OptionTypes and just rename it Options. Remove ALL code about creating id's here in your GUI, that should be handled by the backend. Instead in the GUI there should be a Sortorder property on the Options (which also of course gets stored by the backend). Then when you store, the ones without an id get inserted, the ones with an id get updated. Much easier to handle everything that way.
I'd also break out optionTypes and options to their own services/providers. Much easier to keep track of what needs to be done. And each just basically contains add, remove and maybe a find/getJSON or something.
Here's a restructured version. Much easier to keep track of what belongs where. And it has more features than the original with less code. http://plnkr.co/edit/BHcu6vAfcpEYQpZKHc5G?p=preview