I have an object called main and its one of the item, data: , points to an array defined as submenuitem. When user clicks on something, the UI gets updated with array items in submenuitems. It also updates the topbar to display the title of current menu which is basically the variable name SUBMENUITEMS.
Can anyone tell how can I get the submenu item variable name data: "--->this<--" as String?
let submenuItems = ["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"];
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:submenuItems
}
Edit:
I see a lot of confusion. Actually this a part of my code which is written using Vuejs -
<div v-for="(item,index) in dataArray" v-on:click="subMenu(item)" v-bind:id="'tile_'+index" class="card">
<div><img v-bind:src=item.menuLogo onerror="this.src='img/default.svg'" /></div>
<div>{{item.title}}</div>
<div>{{index+1}}</div>
</div>
let subhome = [
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]},
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]}, {"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]},`
{"title":"sub_card_title","sub_cardimage":"sub_item1.png",data:[]}
]
let home = [
{"title":"card_title","cardimage":"item1.png",data:[]},
{"title":"card_title","cardimage":"item1.png",data:subhome}, {"title":"card_title","cardimage":"item1.png",data:[]},`
{"title":"card_title","cardimage":"item1.png",data:[]}
]
let menu = new Vue({
el: '#menu',
data: {
dataArray: home
},
methods: {
subMenu: function(item) {
if (item.data.length > 0) {
this.dataArray = item.data;
console.log('sub menu array -->', this.dataArray);
$('#menu-title').text(eval(item));
}
},setTitle: function(val) {
let title = eval(val); // <---- how to vaiable name of data array
$('#spancontainer').text(title)
}
}
});
When you do
let submenuItems = ["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"];
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:submenuItems
}
it is equating to
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:["subMenu 1","subMenu 2","subMenu 3","subMenu 4","subMenu 5","subMenu 6"]
}
In order to get "submenuItems", you'd need to pass it as an object and get its key like this
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:{
submenuItems: submenuItems
}
}
console.log(Object.keys(main.data)[0]);
or simply create a new property and set it to "submenuItems" like this
let main = {
title:'main menu',
menuLogo:'img/logo1.png',
data:{
name: 'submenuItems',
submenuItems: submenuItems
}
}
console.log(main.data.name);
Related
In my app, I have a list where the user can add to and delete elements from it. My problem is, when I click an element (it can be in the middle, at the end etc.), it deletes the first element of the list. And when I refresh the page, I can see the previously 'deleted' elements. Like I haven't deleted anything. Here is my code. What's wrong with it and how should I fix it?
HTML:
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row)">block</mat-icon>
</button>
TS:
deleteWorkItem(row: IProduct, index: number) {
let supplierProduct: ISupplierProduct = {
Supplier: {
SupplierId: this.SupplierId
},
Product: {
ProductId: row.ProductId
}
};
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = 'Ürünü silmek istiyor musunuz?';
this.confirmDialogRef.afterClosed().subscribe(result => {
if (result) {
this._service.update('Supplier/DeleteSupplierProduct', supplierProduct).subscribe(response => {
this._customNotificationService.Show('Ürün silindi', 'Tamam', 2);
});
let tempData = this.dataSource.data.slice(0);
tempData.splice(index, 1);
this.dataSource = new MatTableDataSource(tempData);
this.EditIndex = undefined;
this._products = this.dataSource.data;
this.ProductChange.emit(this._products);
}
});
}
You don't seem to pass index into deleteWorkItem method.
You need to declare a template variable within *ngFor as follows:
<div *ngFor="let row of data; let i = index">
...
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row, i)">block</mat-icon>
</button>
</div>
I'm building a key-command resource and giving VueJS a whirl while doing so. I'm a newbie but am gaining the grasp of things (slowly...).
I want to be able to search in a global search form for key commands I'm defining as actions within sections of commands (see data example below). I would like to search through all the actions to show only those that match the search criteria.
My HTML is below:
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
My main Vue instance looks like this:
import Vue from 'vue/dist/vue.esm'
import { commands } from './data.js'
document.addEventListener('DOMContentLoaded', () => {
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
return c.command.filter((item) => {
console.log(item.action)
return item.action.indexOf(self.searchQuery) > -1;
});
});
},
}
});
}
});
And finally the data structure in data.js
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
export { commands };
I'm able to output the commands using the console.log(item.action) snippet you see in the computed method called sectionsSearched.
I see no errors in the browser and the data renders correctly.
I cannot however filter by searching in real-time. I'm nearly positive it's a combination of my data structure + the computed method. Can anyone shed some insight as to what I'm doing wrong here?
I'd ideally like to keep the data as is because it's important to be sectioned off.
I'm a Rails guy who is new to this stuff so any and all feedback is welcome.
Thanks!
EDIT
I've tried the proposed solutions below but keep getting undefined in any query I pass. The functionality seems to work in most cases for something like this:
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
But alas nothing actually comes back. I'm scratching my head hard.
There is a issue in your sectionsSearched as it is returning the array of just commands.
See this one
sectionsSearched() {
return this.commands.reduce((r, e) => {
const command = e.command.filter(item => item.action.indexOf(this.searchQuery) > -1);
const section = e.section;
r.push({
section,
command
});
}, []);
}
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
// the code below return an array, not a boolean
// make this.commands.filter() not work
// return c.command.filter((item) => {
// return item.action.indexOf(self.searchQuery) > -1;
// });
// to find whether there has command action equal to searchQuery
return c.command.find(item => item.action === self.searchQuery);
});
},
}
});
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
Is that work as you wish ?
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
}
since filter will always return an array(empty or not) which value always is true.
I'm making a comment system for a website which gets comments via Ajax request and them adds them to a list of comments. This works fine however it always adds them to the bottom of the list. This is because i'm using list.appendChild(item); for which I now need to find an alternative...
Is there a way to add a <li> element under another <li> of which I know the id?
Here is a video containing current behavior
and the code for that:
function loadcomments(id){
$.ajax({
url: '/ajax/getcomments/',
data: {
'identifier': '{{ identifier }}',
'id': id,
'begin': '0',
'end': '30',
},
dataType: 'json',
success: function (data) {
var json = JSON.parse(data.comments);
console.log(json);
makeUL(json);
function makeUL(array) {
list = document.getElementById('commentlist');
for(var i = 0; i < array.length; i++) {
if(!document.getElementById("comment-id-" + array[i].pk)){
var item = document.createElement('li');
item.className = "commentlistelement"
item.setAttribute('id',"comment-listitem-id-" + array[i].pk)
var maindiv = document.createElement('div')
maindiv.setAttribute('id',"comment-id-" + array[i].pk);
maindiv.className = "row comment";
maindiv.setAttribute("style","padding-left: "+3*array[i].fields.indent+"vw;");
maindiv.innerHTML = "<div class=\"col-md-1\">profile<br>pic</div><div class=\"col-md-9\"><small>"+ array[i].fields.user +" </small><small><button class=\"buttonlink\" onclick=\"reply("+ array[i].pk +")\">reply</button></small> <small><button class=\"buttonlink\" onclick=\"loadcomments("+ array[i].pk +")\">show more</button></small><br><p>" + array[i].fields.description + "</p></div>";
item.appendChild(maindiv);
list.appendChild(item);
}
}
return list;
}
}
});
};
You can probably make use of the firstChild and insertBefore methods of the Node object:
https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild
https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
JQuery also has an insert after method as well:
http://api.jquery.com/insertafter/
Get the first child of the list container. Then insert before it.
You could do something like this.
Since you are using jQuery, there is no need to use the native js api as you will need to be more verbose and won't have the power of jQuery to help manipulate the DOM.
you can simply use $.fn.after(html) or $.fn.append(html) depending on where you need the comment to sit in the comment.
// mock ajax call that will resolve in under 400ms
$.mockAjax = ({ parent }) => {
const $promise = $.Deferred()
setTimeout(() => {
$promise.resolve([
{ id: 11, comment: 'first child comment', parent: 1 },
{ id: 12, comment: 'second child comment', parent: 2 },
{ id: 12, comment: 'second child comment 2', parent: 2 },
{ id: 13, comment: 'click me again', parent: 3 },
{ id: 23, comment: 'third grandchild comment', parent: 13 },
].filter(x => x.parent == parent))
}, Math.random() * 400)
return $promise.promise()
}
// api getter
const getChildComments = parent =>
$.mockAjax({ parent })
// comments renderer
const renderComments = comments =>
$('<ul class="comments">')
.append(comments.map(({ id, parent, comment }) =>
`<li class="comment" data-comment-id="${id}" data-parent-id="${parent}">
${comment}
</li>`
))
// delegate the event
$(document.body).on('click', '.comment', function(e) {
const $this = $(this)
// don't allow the event to bubble up through the dom
e.stopImmediatePropagation()
// only get content if not already loaded
if ($this.data('loaded')) {
$this.toggleClass('hide')
} else {
// set the loading state
$this.addClass('loading')
getChildComments($this.data('comment-id'))
// convert the comments to jQuery objects to be added to the DOM
.then(renderComments)
// append the comments to the DOM inside the clicked comment
.then($comments => $this.append($comments))
// clean up your comment state
.then(() =>
$this
.removeClass('loading')
.data('loaded', true)
)
}
})
.comment {
cursor: pointer;
}
.comment.loading {
color: red;
}
.comment.hide > .comments {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="comments" id="comments">
<li class="comment" data-comment-id="1">has single child comment</li>
<li class="comment" data-comment-id="2">has two child comments comments</li>
<li class="comment" data-comment-id="3">has grandchild comments</li>
<li class="comment" data-comment-id="4">no child comments</li>
</ul>
I have a table with these fields: product, lot, input1, input2. You can clone a line, and you can add a new line.
What I want to do is that for each row you can add a new Lot created by a "number" and by "id" that user write in the input field under the Select lot. And I wanted that the script add the new Lot in the json data and the lot 's option list.
This is the function for add that I tried to do:
$scope.addLot = function() {
var inWhichProduct = row.selectedProduct;
var newArray = {
"number": row.newLot.value,
"id": row.newLot.id
};
for (var i = 0; i < $scope.items.length; i++) {
if ($scope.items[i].selectedProduct === inWhichProduct) {
$scope.items[i].selectedLot.push(newArray);
}
}
};
-->> THIS <<-- is the full code.
Can you help me?
I think your question is a little too broad to answer on Stack Overflow, but here's an attempt:
<div ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat="lot in lots">
<td>{{ lot.id }}</td>
<td>{{ lot.name }}</td>
</tr>
</table>
<p>name:</p> <input type="text" ng-model="inputName">
<p>id:</p> <input type="text" ng-model="inputId">
<button ng-click="addLotButton(inputId, inputName)">Add</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lots = [{
name: "test",
id: 1
},
{
name: "test2",
id: 2
}
];
$scope.addLot = function(lotId, lotName) {
var newLotObject = {
name: lotName,
id: lotId
};
$scope.lots.push(newLotObject);
};
$scope.addLotButton = function(id, name) {
$scope.addLot(id, name);
};
$scope.addLot(3, "Another test");
});
</script>
Basically this code just takes some input and adds an object to the scope for that input. The table is created using an ng-repeat of this data. It's not great code at all but it's just a quick example.
The push method adds newArray to selectedLot array. It's not working on the JSON data but on arrays. If you want to have the JSON, you can give a try to :
var myJsonString = JSON.stringify(yourArray);
It will create a JSON string based on the parameter
Maybe you should try to structure your data to make lots as properties of products.
{
products: [
{id: 1, lots: [{id:1}, {id:2}]},
{id: 2, lots: [{id:1}, {id:2}]}
]
}
To add a lot to a product :
product = products[0];
product.lots.push(newArray);
Change the fallowing:
html:
<button ng-click="addLot(row.selectedProduct.id,row.newLot.value,row.newLot.id)">Add</button>
js:
$scope.addLot = function(id,val,lotId) {
// console.log(id);
var inWhichProduct = id;
var newArray = { "value": val, "id": lotId };
//console.log($scope.items)
angular.forEach($scope.items,function(v,i){
if($scope.items[i].id == id )
{
$scope.items[i].lots.push(newArray);
console.log($scope.items[i].lots);
}
});
};
http://plnkr.co/edit/W8eche8eIEUuDBsRpLse?p=preview
I have an array of menu items, each containing Name and URL like this:
var menuItems = [
{
name : "Store",
url : "/store"
},
{
name : "Travel",
url : "/store/travel"
},
{
name : "Gardening",
url : "/store/gardening"
},
{
name : "Healthy Eating",
url : "/store/healthy-eating"
},
{
name : "Cook Books",
url : "/store/healthy-eating/cook-books"
},
{
name : "Single Meal Gifts",
url : "/store/healthy-eating/single-meal-gifts"
},
{
name : "Outdoor Recreation",
url : "/store/outdoor-recreation"
},
{
name : "Hiking",
url : "/store/outdoor-recreation/hiking"
},
{
name : "Snowshoeing",
url : "/store/outdoor-recreation/hiking/snowshoeing"
},
{
name : "Skiing",
url : "/store/outdoor-recreation/skiing"
},
{
name : "Physical Fitness",
url : "/store/physical-fitness"
},
{
name : "Provident Living",
url : "/store/provident-living"
}
]
I've been trying with no success to render this as an unordered list with a nested UL structure that follows the URL path structure like so:
<ul>
<li>Store
<ul>
<li>Travel</li>
<li>Gardening</li>
<li>Healthy Eating
<ul>
<li>Cook Books</li>
<li>Single Meal Gifts</li>
</ul>
</li>
<li>Outdoor Recreation
<ul>
<li>Hiking
<ul>
<li>Snowshoeing</li>
</ul>
</li>
<li>Skiing</li>
</ul>
</li>
<li>Physical Fitness</li>
<li>Provident Living</li>
</ul>
</li>
</ul>
All of the examples I've seen begin with a data structure that reflects the parent-child relationship (e.g. xml or JSON), but I'm having a very difficult time pulling this out of the URL and using it to render the new structure.
If anyone could please steer me in the right direction for how to do this using jQuery, I'd really appreciate it. I realize I probably need to use some recursive functions or maybe jQuery templates, but these things are still a bit new to me.
Thanks
I think the best solution is firstly to convert your data structure to a tree one, with parent/children relations. Render this structure will then be easier, as the UL itself has a tree structure.
You can convert menuItems using these couple of functions
// Add an item node in the tree, at the right position
function addToTree( node, treeNodes ) {
// Check if the item node should inserted in a subnode
for ( var i=0; i<treeNodes.length; i++ ) {
var treeNode = treeNodes[i];
// "/store/travel".indexOf( '/store/' )
if ( node.url.indexOf( treeNode.url + '/' ) == 0 ) {
addToTree( node, treeNode.children );
// Item node was added, we can quit
return;
}
}
// Item node was not added to a subnode, so it's a sibling of these treeNodes
treeNodes.push({
name: node.name,
url: node.url,
children: []
});
}
//Create the item tree starting from menuItems
function createTree( nodes ) {
var tree = [];
for ( var i=0; i<nodes.length; i++ ) {
var node = nodes[i];
addToTree( node, tree );
}
return tree;
}
var menuItemsTree = createTree( menuItems );
console.log( menuItemsTree );
The resulting menuItemsTree will be an object like this
[
{
"name":"Store",
"url":"/store",
"children":[
{
"name":"Travel",
"url":"/store/travel",
"children":[
]
},
{
"name":"Gardening",
"url":"/store/gardening",
"children":[
]
},
{
"name":"Healthy Eating",
"url":"/store/healthy-eating",
"children":[
{
"name":"Cook Books",
"url":"/store/healthy-eating/cook-books",
"children":[
]
},
{
"name":"Single Meal Gifts",
"url":"/store/healthy-eating/single-meal-gifts",
"children":[
]
}
]
},
{
"name":"Outdoor Recreation",
"url":"/store/outdoor-recreation",
"children":[
{
"name":"Hiking",
"url":"/store/outdoor-recreation/hiking",
"children":[
{
"name":"Snowshoeing",
"url":"/store/outdoor-recreation/hiking/snowshoeing",
"children":[
]
}
]
},
{
"name":"Skiing",
"url":"/store/outdoor-recreation/skiing",
"children":[
]
}
]
},
{
"name":"Physical Fitness",
"url":"/store/physical-fitness",
"children":[
]
},
{
"name":"Provident Living",
"url":"/store/provident-living",
"children":[
]
}
]
}
]
You mentioned you already have html renderer for trees, right? If you need further help let us know!
12 simple lines of code:
var rootList = $("<ul>").appendTo("body");
var elements = {};
$.each(menuItems, function() {
var parent = elements[this.url.substr(0, this.url.lastIndexOf("/"))];
var list = parent ? parent.next("ul") : rootList;
if (!list.length) {
list = $("<ul>").insertAfter(parent);
}
var item = $("<li>").appendTo(list);
$("<a>").attr("href", this.url).text(this.name).appendTo(item);
elements[this.url] = item;
});
http://jsfiddle.net/gilly3/CJKgp/
Although I like the script of gilly3 the script produces list with different element nesting of <li> and <ul> than was originally asked. So instead of
<li>Store
<ul>
<li>Travel</li>
...
</ul>
</li>
It produces
<li>Store
</li>
<ul>
<li>Travel</li>
...
</ul>
This may cause incompatibilities for utilities or frameworks working with such generated menu and producing interactive menu with animation (e.g. superfish.js).
So I updated the 12 lines script
var rootList = $("<ul>").appendTo("body");
var elements = {};
$.each(menuItems, function() {
var parent = elements[this.url.substr(0, this.url.lastIndexOf("/"))];
var list = parent ? parent.children("ul") : rootList;
if (!list.length) {
list = $("<ul>").appendTo(parent);
}
var item = $("<li>").appendTo(list);
$("<a>").attr("href", this.url).text(this.name).appendTo(item);
elements[this.url] = item;
});
http://jsfiddle.net/tomaton/NaU4E/
It's not in jQuery, but maybe this could help. I developed this after seeking the web to do exactly what you want.
http://www.chapleau.info/article/ArrayofUrlsToASitemap.html
Or maybe complete jQuery plugin http://jsfiddle.net/9FGRC/
(EDIT)
An update to previous version http://jsfiddle.net/9FGRC/1/
This version supports following case
var menuItems = [
{
name : "Store",
url : "/store"
},
{
name : "Cook Books",
url : "/store/healthy-eating/cook-books"
},
{
name : "Single Meal Gifts",
url : "/store/healthy-eating/single-meal-gifts"
}
]
Since there is skipped
{
name : "Healthy Eating",
url : "/store/healthy-eating"
},
It will produce following html
<ul>
<li>Store
<ul>
<li>Cook Books</li>
<li>Single Meal Gifts</li>
</ul>
</li>
</ul>
I guess it won't be the case, but could be helpful to someone
try something like this.
function Directory(parentNode) {
//Structure for directories. Subdirectories container as a generic object, initially empty
this.hasSubdirectories = false;
this.subdirectories = {};
//Render in steps. Until subdirectories or a link are added, all it needs is an LI and a blank anchor
this.nodeLi = document.createElement("li");
parentNode.appendChild(this.nodeLi);
this.nodeA = document.createElement("a");
this.nodeLi.appendChild(this.nodeA);
//if a subdirectory is added, this.nodeUl will be added at the same time.
}
Directory.prototype.setLabel = function (sLabel) {
this.nodeA.innerHTML = sLabel;
}
Directory.prototype.setLink = function (sLink) {
this.nodeA.href = sLink;
}
Directory.prototype.getSubdirectory = function (sPath) {
//if there were no previous subdirectories, the directory needs a new UL node.
if (!this.hasSubdirectories) {
this.nodeUl = document.createElement("ul");
this.nodeLi.appendChild(this.nodeUl);
this.hasSubdirectories = true;
}
//split the path string into the base directory and the rest of the path.
var r = /^\/?(?:((?:\w|\s|\d)+)\/)(.*)$/;
var path = r.exec(sPath);
//if the desired path is in a subdirectory, find or create it in the subdirectories container.
var subDirName = path[1] || path[2];
var subDir;
if (this.subdirectories[subDirName] === undefined) this.subdirectories[subDirName] = new Directory(this.nodeUl);
subDir = this.subdirectories[subDirName];
if (path[1] && path[2]) {
return subDir.getSubdirectory(path[2]);
} else {
return subDir;
}
}
function main(whichNode, aMenuItems) {
//whichNode is the node that is to be the parent of the directory listing.
//aMenuItems is the array of menu items.
var i;
var l = aItems.length;
var topDir = new Directory(whichNode);
//for each menu item, add a directory and set its properties.
var dirToAdd;
for (i = 0; i < l; i++) {
dirToAdd = topDir.getSubdirectory(aMenuItems[i].url);
dirToAdd.setLabel(aMenuItems[i].name);
dirToAdd.setLink(aMenuItems[i].url);
}
//and that's it.
}
how's that work?