I'm building my little lib js.
my intent is to have both possibilities in this way:
//this:
core.find(".myclass").myAction (actor);
var target = core.target (".myclassname");
core.myAction (target, actor);
//or:
core.find(".myclass").myAction (actor);
var target = core.find(".myclassname");
core.myAction (target, actor);
//so, return the finded for core and gearcore, but
//I can not and I do not think it's feasible (is it?)
//this isn't a good solution... thanks.
//var target = core.find(".myclassname");
//target .myAction (target, actor);
anyway... the first is chained, the second part from the first class and generates the action through a parameter.
yes, I already know that I can create a copy of the functions, but it's disgusting ... and copy the target how can I solve it?
here it's about joining the two "find" or putting the two return or joining the two return or something else or maybe something similar.
for convenience: lib in jsfiddle else...
LIB:
note 1:the two eval functions are just a demo to get an answer. the complete lib contains more and I need to assign the target in both ways
class coregears {
constructor(gear) {
this.gear = gear;
}
actor(target, fname) {
alert("inside actor");
let t_, f_;
(1 == arguments.length) ? (t_ = this.gear, f_ = target) : (t_ = target, f_ = fname);
t_.forEach((looped) => { eval(f_)(looped); return this.gear});
}
}
class Core {
// return for core.function(target,actor);
target(subject)
{
let name;
var finded;
if (subject.startsWith(".")) {
name = subject.split(".")[1];
finded = [...document.getElementsByClassName(name)];
}
if (subject.match(/\B\#\w\w+\b/g)) {
name = subject.replace(/#(\S*)/g, 'ID_$1').split("ID_")[1];
finded = [...document.querySelectorAll("#" + name)];
}
if (subject.startsWith("[")) {
finded = [...document.querySelectorAll(subject)];
}
return finded;
}
// return for core.find(target).function(actor);
find(subject){
let name;
if (subject.startsWith(".")) {
name = subject.split(".")[1];
find = [...document.getElementsByClassName(name)];
}
if (subject.match(/\B\#\w\w+\b/g)) {
name = subject.replace(/#(\S*)/g, 'ID_$1').split("ID_")[1];
find = [...document.querySelectorAll("#" + name)];
}
if (subject.startsWith("[")) {
find = [...document.querySelectorAll(subject)];
}
return new coregears(find);
}
}
const core = new Core();
SCRIPT:
//chain
core.find(".test_Y").actor(myactor_1);
function myactor_1(looped) {
alert("actor_1");
looped.style = "font-weight: bold; color: magenta;";
};
//direct
var mytarget = core.target(".test_X");
core.actor(mytarget,myactor_2);
function myactor_2(looped) {
alert("actor_2");
looped.style = "font-weight: bold; color: green;";
};
HTML:
<div class="test_X">SIMPLE TEST CLASS</div>
<div class="test_X">SIMPLE TEST CLASS</div>
<div class="test_X">SIMPLE TEST CLASS</div>
<div class="test_Y">SIMPLE TEST CLASS</div>
<div class="test_Y">SIMPLE TEST CLASS</div>
<div class="test_Y">SIMPLE TEST CLASS</div>
Idea?
update: I replaced the eval with f_.call(this,looped); thank you #Shilly.
Related
Here is a simple example of a group of divs that can be sorted either by their HTML content or by custom attribute values:
Sort alphabetically by HTML content
Sort low to high by the value of the attribute "sortweight"HTML:
I have tried following code and got error
ReferenceError: $$ is not defined
HTML
<div id="sortexample">
<div class="sortitem" sortweight="5">Pears [sortweight: 5]</div>
<div class="sortitem" sortweight="3">Apples [sortweight: 3]</div>
<div class="sortitem" sortweight="1">Cherries [sortweight: 1]</div>
<div class="sortitem" sortweight="4">Oranges [sortweight: 4]</div>
<div class="sortitem" sortweight="2">Strawberries [sortweight: 2]</div>
</div>
JavaScript:
// sort all divs with classname 'sortitem' by html content
function sort_div_content() {
// copy all divs into array and destroy them in the page
divsbucket = new Array();
divslist = $$('div.sortitem');
for (a=0;a<divslist.length;a++) {
divsbucket[a] = divslist[a].dispose();
}
// sort array by HTML content of divs
divsbucket.sort(function(a, b) {
if (a.innerHTML.toLowerCase() === b.innerHTML.toLowerCase()) {
return 0;
}
if (a.innerHTML.toLowerCase() > b.innerHTML.toLowerCase()) {
return 1;
} else {
return -1;
}
});
// re-inject sorted divs into page
for (a=0;a<divslist.length;a++) {
divsbucket[a].inject($('sortexample'));
}
}
// sort by attributes - usage for our example: sort_div_attribute('sortweight');
function sort_div_attribute(attname) {
// copy all divs into array and destroy them in the page
divsbucket = new Array();
divslist = $$('div.sortitem');
for (a=0;a<divslist.length;a++) {
divsbucket[a] = new Array();
// we'vev passed in the name of the attribute to sort by
divsbucket[a][0] = divslist[a].get(attname);
divsbucket[a][1] = divslist[a].dispose();
}
// sort array by sort attribute content
divsbucket.sort(function(a, b) {
if (a[0].toLowerCase() === b[0].toLowerCase()) {
return 0;
}
if (a[0].toLowerCase() > b[0].toLowerCase()) {
return 1;
} else {
return -1;
}
});
// re-inject sorted divs into page
for (a=0;a<divslist.length;a++) {
divsbucket[a][1].inject($('sortexample'));
}
}
I dont know why i got this error
On the fifth line, it says divslist = $$('div.sortitem');, but $$ is not defined. You have to use $ instead of $$.
divslist = $('div.sortitem'); This would be better
I have a HTML/JS code as shown below in which on click of Execute button, I want to display:
[{"detail":"Hello World"},{"detail":"Great News"}]
Currently, on clicking Execute button I am getting the following:
[{"detail":""},{"detail":""}]
I am wondering what changes I need to make in the JS code below so that on click of a Execute button, I am able to display:
[{"detail":"Hello World"},{"detail":"Great News"}]
HTML:
<input type="submit" onClick="runCode()" value="Execute" >
<div id="console-log">
</div>
Javascript:
$(document).ready(function() {
})
function runCode(){
var td=new Todo()
td.addTask("Hello World")
td.addTask("Great News")
td.printList()
}
class Todo{
constructor(name) {
this.todolist = [];
this.task={
'detail':''
}
}
addToList(newobj)
{
this.todolist.push(newobj)
}
addTask(taskDetail){
this.task.detail=taskDetail
this.todolist.push(this.task)
this.task.detail='' //clear data for next step
}
printList()
{
var consoleLine = "<p class=\"console-line\"></p>";
var text= JSON.stringify(this.todolist)
$("#console-log").append($(consoleLine).html(text));
//alert(this.todolist)
}
}
Push an object created just from the argument into the todolist property:
addTask(detail) {
this.todolist.push({ detail });
}
The this.task property only makes things more confusing, I'd recommend avoiding it.
function runCode() {
var td = new Todo()
td.addTask("Hello World")
td.addTask("Great News")
td.printList()
}
class Todo {
constructor(name) {
this.todolist = [];
}
addTask(detail) {
this.todolist.push({ detail });
}
printList() {
var consoleLine = "<p class=\"console-line\"></p>";
var text = JSON.stringify(this.todolist)
$("#console-log").append($(consoleLine).html(text))
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="submit" onClick="runCode()" value="Execute">
<div id="console-log">
</div>
It would also be trivial to remove the dependency on the big jQuery library if you wanted, it's not accomplishing anything useful. Also, it'd be good practice to use addEventListener instead of an inline handler:
document.querySelector('input').addEventListener('click', () => {
var td = new Todo()
td.addTask("Hello World")
td.addTask("Great News")
td.printList()
});
class Todo {
constructor(name) {
this.todolist = [];
}
addTask(detail) {
this.todolist.push({ detail });
}
printList() {
document.querySelector('code').appendChild(document.createElement('p'))
.textContent = JSON.stringify(this.todolist);
}
}
<input type="submit" value="Execute">
<code>
</code>
I have coded a ajax based "JS TABS" containing .JSON file like 10 months ago, now wanted to reuse it, and can't find out why it's not working. I haven't touched it since and don't know where is the bug.
When i click the button to render products nothing prints out - except console telling me: items is undefined = so i moved it inside function changeCategoryItems(categoryId) { } well no errors but nothing renders...can someone help me ?
Here is a codepen reference of what i mean: https://codepen.io/Contemplator191/pen/WNwgypY
And this is JSON : https://api.jsonbin.io/b/5f634e0c302a837e95680846
If codepen is not suitable/allowed here is whole JS for that
let items = [];
const buttons = document.querySelectorAll('button');
const wrapper = document.querySelector('section.products');
buttons.forEach(function (button) {
button.addEventListener('click',event => {
changeCategoryItems(event.target.dataset.category);
});
});
function changeCategoryItems(categoryId) {
let items = [];
const buttons = document.querySelectorAll('button');
const wrapper = document.querySelector('section.products');
const viewItems = (categoryId == 0 ) ? items : items.filter(item => item.category == categoryId);
wrapper.innerHTML = "";
viewItems.forEach(item => {
const div = document.createElement('div');
div.setAttribute("class", "product");
div.innerHTML = createItem(item);
wrapper.appendChild(div);
});
};
function createItem(item) {
return `
<div class="product__img">
<img src="${item.img}" class="">
</div>
<div class="product__name _tc">
<h4 class="">${item.heading}</h4>
</div>
<div class="text-desc product__desc">
<p class="">${item.description}</p>
</div>
<div class="product__bottom-content">
<span class="product__info">${item.info}</span>
${item.btn}
</div>
`
}
fetch('https://api.jsonbin.io/b/5f634e0c302a837e95680846')
.then(function (res) { return res.json() })
.then(function (data) {
items = data.items;
changeCategoryItems(1);
});`
In your fetch you're trying to assign data.items to the items variable but the api doesn't return data with an items node so items is undefined. It's possible the api changed their return format since the last time you used it which would explain why it worked previously.
this seems to fix it
.then(function (data) {
items = data;
changeCategoryItems(1);
});
Your issue is in this line:
items = data.items;
Now, the returned value is an array, hence you can use it as it is.
The updated codepen
Is there a way to simplify the below code by using an array? For example, when button 1 (with the index of 0) in the HTML is clicked, could that be used to get a value at index 0 in another array?
function f1() {
document.getElementById("dis").innerHTML = "JoeMae";
}
function f2() {
document.getElementById("dis").innerHTML = "TanakaMae";
}
function f3() {
document.getElementById("dis").innerHTML = "James";
}
function f4() {
document.getElementById("dis").innerHTML = "Deus";
}
<button onclick="f1()">no.1</button>
<button onclick="f2()">no.2</button>
<button onclick="f3()">no.3</button>
<button onclick="f4()">no.4</button>
<p id="dis"></p>
You can simplify without using array:
<button onclick="f('JoeMae')">no.1</button>
<button onclick="f('TanakaMae')">no.2</button>
<button onclick="f('James')">no.3</button>
<button onclick="f('Deus')">no.4</button>
<p id="dis"></p>
function f(str) {
document.getElementById("dis").innerHTML = str;
}
Use another array such that the nth index of that array corresponds to the nth button:
const texts = [
"JoeMae",
"TanakaMae",
"James",
"Deus"
];
const dis = document.getElementById("dis");
document.querySelectorAll('button').forEach((button, i) => {
button.addEventListener('click', () => {
dis.textContent = texts[i];
});
});
<button>no.1</button>
<button>no.2</button>
<button>no.3</button>
<button>no.4</button>
<p id="dis"></p>
Note that unless you're deliberately inserting HTML markup, you should probably use textContent, not innerHTML. (textContent is faster and safer)
Here's an approach that's vanilla JS. I used the dataset API to connect each button to its data, then a single handler to retrieve and display this data.
"use strict";
function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
var responseArray = ['JoeMae', 'TanakaMae', 'James', 'Deus'];
responseArray.forEach( function(arrElem, elemIndex, arr)
{
var btn = newEl('button');
btn.textContent = `no.${elemIndex+1}`;
btn.dataset.response = arrElem;
btn.addEventListener('click', onClick, false);
document.body.appendChild(btn);
}
);
function onClick(evt)
{
let text = this.dataset.response;
byId('dis').textContent = text;
}
}
<p id='dis'></p>
Here's a slightly cleaner and more flexible example how to implement this type of functionality.
If you are having a lot of rendering functionality like this, I would recommend you to use a library/framework for it, though.
const buttonDefinitions = [
{title: 'no.1', name: 'Monica'},
{title: 'no.2', name: 'Erica'},
{title: 'no.3', name: 'Rita'},
{title: 'no.4', name: 'Tina'}
];
const buttonContainer = document.getElementById('buttonContainer');
const resultContainer = document.getElementById('resultContainer');
for (const buttonDefinition of buttonDefinitions) {
const button = document.createElement('button');
button.innerHTML = buttonDefinition.title;
button.onclick = () => {
resultContainer.innerHTML = buttonDefinition.name;
};
buttonContainer.appendChild(button);
}
<div id="buttonContainer"></div>
<div id="resultContainer"></div>
You can pass the element to the function and access the element data-attributes
In the below example I am passing data-name
function f(element) {
document.getElementById("dis").innerHTML = element.dataset["name"];
}
<button data-name="JoeMae" onclick="f(this)">no.1</button>
<button data-name="TanakaMae" onclick="f(this)">no.2</button>
<button data-name="James" onclick="f(this)">no.3</button>
<button data-name="Deus" onclick="f(this)">no.4</button>
<p id="dis"></p>
I have the following html:
<div ng-show=showMarketingNav>
...
</div>
<div ng-show=showProductsNav>
...
</div>
<div ng-show=showSettingsNav>
...
</div>
What I want to do is to easily be able to hide all but one of the divs from another controller. I thought I could be clever and do the following:
var subNavMenuDisplays = {
Marketing: $scope.showMarketingNav,
Products: $scope.showProductsNav,
Settings: $scope.showSettingsNav
}
$rootScope.hideContextMenu = function () {
for (var category in subNavMenuDisplays) {
subNavMenuDisplays[category] = false;
}
}
$rootScope.setContextMenu = function (name) {
$rootScope.hideContextMenu();
subNavMenuDisplays[name] = true;
}
but this obviously does not work as $scope.showMarketingNav etc. will be passed as value, not reference.
The following works, but is not exactly nice to work with:
$rootScope.hideContextMenu = function () {
$scope.showMarketingNav = false;
$scope.showProductsNav = false;
$scope.showSettingsNav = false;
}
$rootScope.setContextMenu = function (name) {
$rootScope.hideContextMenu();
if (name == "Marketing") {
$scope.showMarketingNav = true;
}
if (name == "Products") {
$scope.showProductsNav = true;
}
if (name == "Settings") {
$scope.showSettingsNav = true;
}
}
Is there a way to grab $scope.showMarketingNav by reference, or another clever way around this?
I'd prefer not using eval to concatenate variable names.
You can place an object on the $scope and then toggle it dynamically:
$scope.show = {};
$rootScope.setContextMenu = function (name) {
$scope.show = {};
$scope.show[name] = true;
}
And the Html:
<div ng-show="show.Marketing">
...
</div>
<div ng-show="show.Products">
...
</div>
<div ng-show="show.Settings">
...
</div>
Here's a plunker demonstrating the change.
You can assign simple updater functions in that object:
Marketing: function(val) { $scope.showMarketingNav = val },
Products: function(val) { $scope.showProductsNav = val},
Settings: function(val) { $scope.showSettingsNav = val}
Then call it:
subNavMenuDisplays[name](true);