So I have an html file where an 'Add' button creates field box labeled
<a id="document_create:nxl_item:nxw_referenceHeaderList:0:nxw...." class="button smallButton listWidgetActionAdd" onclick="return ... </a>
If I keep clicking the 'Add' button, the id name continually changes every time where the 0 is. It can be:
document_create:nxl_item:nxw_referenceHeaderList:0:nxw....
document_create:nxl_item:nxw_referenceHeaderList:1:nxw....
document_create:nxl_item:nxw_referenceHeaderList:2:nxw....
document_create:nxl_item:nxw_referenceHeaderList:2:nxw....
document_create:nxl_item:nxw_referenceHeaderList:3:nxw....
Is there a way that I can extract the ID name without having to type it in manually? Like a loop or a function that can extract the ID from a class or something?
Any help would be appreciated! Thanks!
It would be a lot easier to help you if you showed some HTML context, but if new ones are being added after previous ones, you can find them by class (assuming some combination of the classes on them are unique) or based on where they are in the DOM structure, and use the last one:
var list = document.querySelectorAll("a.listWidgetActionAdd"),
last = list[list.length - 1];
if (last) {
// Use `last`, it's a reference to the element
// `last.id` is its `id` if you need it
}
Live Example:
var list = document.querySelectorAll("a.listWidgetActionAdd"),
last = list[list.length - 1];
if (last) {
// Use `last`, it's a reference to the element
// `last.id` is its `id` if you need it
document.body.innerHTML = "<code>last.id = " + last.id + "</code>";
}
<a id="document_create:nxl_item:nxw_referenceHeaderList:0:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="document_create:nxl_item:nxw_referenceHeaderList:1:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="document_create:nxl_item:nxw_referenceHeaderList:2:nxw...." class="button smallButton listWidgetActionAdd"></a>
Re your comment:
I should've mentioned this earlier that there's another add button within the field that's being created that has a similar class. Is there any way to distinguish them both? Like using a string RegExp? The difference is that the id names are different... Would that help?
If it's just a similar class name, then there's no problem — just use the other class name.
If it's the same class name, then yes, you can use the id to differentiate them. For instance, here's the above using an attribute starts with selector to only look at a elements with an id that starts with document_create:nxl_item:nxw_referenceHeaderList:
var list = document.querySelectorAll("a[id^='document_create:nxl_item:nxw_referenceHeaderList']"),
last = list[list.length - 1];
if (last) {
// Use `last`, it's a reference to the element
// `last.id` is its `id` if you need it
document.body.innerHTML = "<code>last.id = " + last.id + "</code>";
}
var list = document.querySelectorAll("a[id^='document_create:nxl_item:nxw_referenceHeaderList']"),
last = list[list.length - 1];
if (last) {
// Use `last`, it's a reference to the element
// `last.id` is its `id` if you need it
document.body.innerHTML = "<code>last.id = " + last.id + "</code>";
}
<a id="document_create:nxl_item:nxw_referenceHeaderList:0:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="some-other-format:0:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="document_create:nxl_item:nxw_referenceHeaderList:1:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="some-other-format:1:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="document_create:nxl_item:nxw_referenceHeaderList:2:nxw...." class="button smallButton listWidgetActionAdd"></a>
<a id="some-other-format:2:nxw...." class="button smallButton listWidgetActionAdd"></a>
Related
I'm building a To Do list app and have a question regarding OOP and JavaScript. I want to create a value in the Constructor that holds my taskBody which contains the HTML and template literal that will be assigned by either the input value or an eventual population from local storage. My goal is to re-use this HTML in two separate functions, but I'm stuck with the template literal.
class Task {
constructor() {
let taskValue //Initializing a variable
this.taskBody = `<div class="task">
<span>${taskValue}</span> //Template Literal
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>`;
}
addTask = () => {
//Prevent empty task
if (this.input.value == "") {
this.setPlaceholder("Please Enter A Task");
return;
}
this.taskValue = this.input.value; //Trying to reassign taskValue to the input value
this.results.innerHTML += this.taskBody; //Trying to grab the HTML from the constructor and populate with taskValue able
ls.setLS(this.taskValue); //setting the Local Storage the Task Value, which works
};
}
I expect if I type "Stack Overflow" in the to-do list, "Stack Overflow" populates in the HTML and the Local Storage, however, it only populates in the Local Storage. The todo item is either undefined, null, or empty.
I've tried using this.taskValue, let taskValue = "", and let taskValue = null, but I get the results described above. Where am I going wrong, and more specifically, how can I reuse the HTML in different functions?
Here's a CodePen where you can see the issue:
Codepen
When you first instantiate the Task, the value of the this.taskBody is set as below:
<div class="task">
<span>undefined</span>
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>
with undefined value, because at the moment of instantiation, the taskValue is undefined.
If your todo list items are added dynamically (which is the case), consider having a function which will enable dynamic replacement, like:
getTaskBody = item => `<div class="task">
<span>${item}</span>
<span class="actions">
<a class="edit-button" title="Edit Task">Edit</a>
<button class="complete-button" title="Complete Task"><i class="fas fa-check"></i></button>
</span>
</div>`;
and use it later in line 123, instead of:
this.results.innerHTML += this.taskBody;
do:
this.results.innerHTML += getTaskBody(this.taskValue);
I have been using some code to add divs to my buttons for styling using the code below, it works fine on the first instance of the button but it does not add the divs to any buttons after that? what am I missing? I'm willing to learn and have tried to Google this but I'm getting buried deep in things I don't fully understand just yet. Would be great if any answer could be in plain Javascript and not jQuery.
JS
// Parent Element
const el = document.querySelector(".myclass");
// Create New Element
const newEl = document.createElement("span");
newEl.classList= "cm-bg";
const newEl2 = document.createElement("span");
newEl2.classList= "cm-base";
// Insert New Element BEFORE an Element
el.before(newEl);
el.before(newEl2);
HTML
<div class="elementor-button-wrapper">
<a href="#" class="elementor-button-link" role="button">
<span class="elementor-button-content-wrapper">
<span class="elementor-button-text">Click here</span>
</span>
</a>
</div>
querySelector finds the first matching element in the document. So your code always adds elements to the first .myclass in your document.
If you want to find all matching elements and update them, you use querySelectorAll and loop through the results:
const list = document.querySelectorAll(".elementor-button-wrapper");
for (const el of list) {
// Create New Element
const newEl = document.createElement("span");
newEl.className = "cm-bg"; // *** See comment on question
const newEl2 = document.createElement("span");
newEl2.className = "cm-base"; // *** See comment on question
// Insert New Element AFTER an Element
el.before(newEl);
el.before(newEl2);
}
Live Example:
const list = document.querySelectorAll(".elementor-button-wrapper");
for (const el of list) {
// Create New Element
const newEl = document.createElement("span");
newEl.className = "cm-bg"; // *** See comment on question
const newEl2 = document.createElement("span");
newEl2.className = "cm-base"; // *** See comment on question
// Insert New Element AFTER an Element
el.before(newEl);
el.before(newEl2);
}
.cm-bg::after {
content: "cm-bg"
}
.cm-base::after {
content: "cm-base"
}
<div class="elementor-button-wrapper">
<a href="#" class="elementor-button-link" role="button">
<span class="elementor-button-content-wrapper">
<span class="elementor-button-text">Click here</span>
</span>
</a>
</div>
<div class="elementor-button-wrapper">
<a href="#" class="elementor-button-link" role="button">
<span class="elementor-button-content-wrapper">
<span class="elementor-button-text">Click here</span>
</span>
</a>
</div>
<div class="elementor-button-wrapper">
<a href="#" class="elementor-button-link" role="button">
<span class="elementor-button-content-wrapper">
<span class="elementor-button-text">Click here</span>
</span>
</a>
</div>
<div class="elementor-button-wrapper">
<a href="#" class="elementor-button-link" role="button">
<span class="elementor-button-content-wrapper">
<span class="elementor-button-text">Click here</span>
</span>
</a>
</div>
That relies on the NodeList from querySelectorAll being iterable, which it is in modern environments (and per specification). If you need to handle older environments, see my answer here for how to polyfill it. Or just use a for loop:
for (let i = 0; i < list.length; ++i) {
const el = list[i];
// ...rest of loop body here...
}
Side note: Beware that no version of IE supports the before method on ChildNode. IE is actively being discontinued by Microsoft, but still has significant presence in large corporate or government installations.
FWIW, you can use insertAdjacentHTML which is universally supported and lets you write the elements as HTML (if that's desireable):
const list = document.querySelectorAll(".elementor-button-wrapper");
for (const el of list) {
el.insertAdjacentHTML(
"beforestart",
"<span class=cm-bg></span><span class=cm-base></span>"
);
}
Or just use insertBefore:
const list = document.querySelectorAll(".elementor-button-wrapper");
for (const el of list) {
// Create New Element
const newEl = document.createElement("span");
newEl.className = "cm-bg"; // *** See comment on question
const newEl2 = document.createElement("span");
newEl2.className = "cm-base"; // *** See comment on question
// Insert New Element AFTER an Element
el.parentElement.insertBefore(newEl, el);
el.parentElement.insertBefore(newEl2, el);
}
Currently i have a simple program that receives user text input. The inputted text is then used as a heading for bootstrap collapsible's, these headings are referred to as 'Categories' within the context of my program. The user is able to click on any one of these categories and assign sub-categories (items) to them. However, i need to be able to record the ID of the category clicked (the category to which the user wishes to add sub-categorie(s) to), and then record this ID in a text file, as the foreign key of the sub-categorie(s) added.
It will work something like this:
1.) User clicks category 'Homework' (Has an ID (Primary Key) of 0)
2.) User adds several sub-categories, namely;
'Math Homework' (Has an ID (Primary Key) of 0, and Foreign key of 0)
'Biology' (Has an ID (Primary Key) of 1, and Foreign key of 0)
'French' (Has an ID (Primary Key) of 2, and Foreign key of 0)
3.) The text file in which the sub-categories are recorded will have the following format:
Math Homework,0,0
Biology,1,0
French,2,0
Note: As it stands, my text file is currently recording the first two parts of the above example, eg: it currently records: Math Homework,0 ... However i am struggling to link a Foreign Key.
Below is the view i am using to display the collapsibles:
#Model.CategoryList.result
#if (Model.CategoryList.result == "")
{
int count = 0;
foreach (String dataLine in Model.CategoryList.userData)
{
string countString = count.ToString();
string target = "dataLine" + countString;
string trigger = "#" + target;
<p>
<a data-toggle="collapse" href="#trigger" role="button" aria-expanded="false" aria-controls="collapseExample">
#dataLine.Split(Model.CategoryList.delimiterChar)[0]
</a>
<button class="btn" onclick="location.href='#Url.Action("Items", "Items")'" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"><i class="fas fa-plus secondaryPlusIcon" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"></i></button>
<button class="btn" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"><i class="far fa-edit secondaryEditIcon" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"></i></button>
<button class="btn" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"><i class="far fa-trash-alt secondaryDeleteIcon" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]"></i></button>
</p>
foreach (String dataLineItem in Model.ItemList.userDataItems)
{
<div class="collapse" id="#target">
<div class="card card-body w-25 p-3 collapsible" id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]">
#dataLineItem.Split(Model.CategoryList.delimiterChar)[0]
</div>
</div>
}
count++;
}
}
As an additional side note; onclick="location.href='#Url.Action("Items", "Items")'" is responsible for redirecting the user to a new view where he/she is able to enter the desired sub-categories.
And id="#dataLine.Split(Model.CategoryList.delimiterChar)[1]" assigns a unique accumulating integer ID, starting from 0, to each new category that is added. This would be the Primary Key for the category headers.
Lastly, here is the controller i have used to initialize the values seen within the view:
public ActionResult Index()
{
CategoryItemViewModel CIVM = new CategoryItemViewModel();
CIVM.ItemList = GetItemModel();
CIVM.CategoryList = GetCategoryModel();
return View(CIVM);
}
public Category GetCategoryModel()
{
var dataFile = Server.MapPath("~/App_Data/Category.txt");
Category cModel = new Category()
{
result = "",
delimiterChar = new[] { ',' },
userData = System.IO.File.ReadAllLines(dataFile) //Category Text File
};
return cModel;
}
public Item GetItemModel()
{
var dataFileItems = Server.MapPath("~/App_Data/Item.txt");
Item iModel = new Item()
{
userDataItems = System.IO.File.ReadAllLines(dataFileItems) //Items Text File
};
return iModel;
}
Hopefully i have provided sufficient information with the code and context mentioned above. I imagine one would use jQuery/AJAX/Json/JS etc to perform such a function, however i am quite lost on where to begin.
Any help is greatly appreciated. Thank you so much!
I tried app developing with Framework7.
I print my array (list) in this way:
if (list != null){
for (var i=0; i<list.length; i++){
output = output + '<li class="swipeout"><div class="item-content swipeout-content"><div class="item-inner"><div class="item-title-row"><div class="item-title">' + list[i].name + '</div></div><div class="item-subtitle">' + new Date(list[i].fDate).toLocaleDateString() + '</div></div></div><div class="swipeout-actions-right">Delete</div></li>';
}
}
$$('#liste').html(output);
When I swipeout an entry, the entry will disappear but he is still in the array.
This is to handle the remove-event:
$$('.swipeout').on('deleted', function () {
myApp.alert('Item removed');
});
How can I get the index of the element to remove it also from the array?
Alternatively, is there an other way to solve this problem?
Thank you!
Markus
If I were you, I'd rather use Framework7's view engine to render the swipeout items and take advantage of the #index helper. Click here for further information.
In your markup, you'd have something similiar to this:
<div class="list-block">
<ul>
{{#each item in list}}
<li class="swipeout">
<!-- Usual list element wrapped with "swipeout-content" -->
<div class="swipeout-content">
<!-- Your list element here -->
<div class="item-content">
<div class="item-media">...</div>
<div class="item-inner">...</div>
</div>
</div>
<!-- Swipeout actions right -->
<div class="swipeout-actions-right">
<!-- Swipeout actions links/buttons -->
<a href="#" data-index={{#index}}>Action 1</a>
<a class="swipeout-close" href="#" data-index={{#index}}>Action 2</a>
</div>
</li>
{{/each}}
Notice that I'm using the "each" helper along with "#index" to render the items and put an attribute on them with the id. But you can still achieve the same objective by using the "i" variable inside the for loop:
if (list != null){
for (var i=0; i<list.length; i++){
output = output + '<li class="swipeout"><div class="item-content swipeout-content"><div class="item-inner"><div class="item-title-row"><div class="item-title">' + list[i].name + '</div></div><div class="item-subtitle">' + new Date(list[i].fDate).toLocaleDateString() + '</div></div></div><div class="swipeout-actions-right"><a href="#" class="swipeout-delete" data-index='+i+'>Delete</a></div></li>';
}
}
When the event is fired:
$$('.swipeout').on('deleted', function () {
var $thisAction = $(this);
// Here you delete the item
delete list[$thisAction.data('index')];
myApp.alert('Item removed');
});
In this case you can't use Array.slice because if you delete item 2 from the markup, the element with the index 3 will replace item 2. The problem with the above approach is that you have to take care of the "holes" in your array. A much better approach would be to use a two-way binding framework, such as VueJs.
I know I can use jQuery's .filter() for filtering by attribute. I also know that usage of such custom attributes is frowned upon since W3C's official implementation of data-thing in HTML5.
But I still want to know why this is not working.
JS:
$("[id^=hide]").click(function() {
var lvl = $(this).attr('lvl');
var id = $(this).attr('tar');
$('#' + id).slideToggle(100);
$('div').filter(function(lvl) {
return $('this').attr('lvl') > lvl;
}).slideToggle(100);
});
HTML:
<a id="hide1" lvl="0" tar="1"></a>
<div id="1" lvl="0"></div>
<a id="hide2" lvl="1" tar="2"></a>
<div id="2" lvl="1"></div>
<a id="hide3" lvl="2" tar="3"></a>
<div id="3" lvl="2"></div>
<a id="hide4" lvl="2" tar="4"></a>
<div id="4" lvl="2"></div>
<a id="hide5" lvl="1" tar="5"></a>
<div id="5" lvl="1"></div>
<a id="hide7" lvl="2" tar="7"></a>
<div id="7" lvl="2"></div>
What I want to achieve is.
1. Click on the link.
2. SlideToggle the div that is close to the link.
3. SlideToggle all other divs that have level attribute bigger than level attribute of that link.
I was also trying to do it like this:
$("[id^=hide]").click(function() {
var lvl = $(this).attr('lvl');
var id = $(this).attr('tar');
$('#' + id).slideToggle(100);
hideOthers(lvl);
});
var hideOthers = function(lvl) {
$('div').filter(function(lvl) {
return $('this').attr('lvl')> lvl;
}).slideToggle(100);
}
But that's also not working. Any ideas?
Updated Fiddle
In this line on your code :
$('div').filter(function(lvl) {
The lvl considered as index in jquery filter() function, so the value of lvl inside the function is [ 0 - 1 - 2 .... nbr_of_divs ].
to fix that just remove lvl from param and use it directly.
$('div').filter(function() {
return $(this).attr('lvl') > lvl;
}).slideToggle(100);
Removing also apostrophes ( ' ) from $(this) inside filter function :
Replacing :
return $('this').attr('lvl')> lvl;
By :
return $(this).attr('lvl') > lvl;
And it work, Hope that this help.
you can use turnery operator here:
Like
return $('this').attr('lvl')> lvl ? lvl : $('this').attr('lvl');