The view in my html is not getting filtered on selecting any li element.
But when I console the filter functions the output generated is correct.Also how to clear the filter so it is reusable again.I'm getting a blank page on clicking open or close select elements.Can anyone help me with this.
I have used two filters in a controller inside the functions like this-
indexController Functions-
this.UserTickets = ()=> {
//code to get the tickets
}
this.openTickets = () => {
index.filteredTickets = $filter('filter')(index.tickets, { status: "open" } );
console.log(index.filteredTickets);
};
//filter closed tickets
this.closeTickets = () => {
index.filteredTickets = $filter('filter')(index.tickets, { status: "close" } );
console.log(index.filteredTickets);
};
this.clearFilter = () => {
//clear the filter
};
HTML-
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a ng-click="indexCtrl.clearfilter()">None</a></li>
<li><a ng-click="indexCtrl.openTickets()">Open</a></li>
<li><a ng-click="indexCtrl.closeTickets()">Close</a></li>
</ul>
<div ng-repeat="ticket in indexCtrl.tickets | filter:tickets |filter:indexCtrl.filteredTickets">
<div class="ticket-no">
<h4>Ticket No:<span>{{ticket}}</span></h4>
</div>
<div class="ticket-title">
<a ng-href="/ticketView/{{ticket.ticketid}}"><h3>{{ticket.title}}</h3></a>
</div>
<div class="ticket-info">
<p class="pull-left">{{ticket.username}} On {{ticket.created | date:"MMM d, y h:mm a"}}</p>
<p class="pull-right">Status:<span>{{ticket.status}}</span></p>
</div>
<hr class="hr">
</div>
You are mixing both angular filter options. I would recommend the javascript filters, index.filteredTickets=$filter('filter')(index.tickets,{status:"open"}); rather than the html template syntax, ng-repeat="ticket in indexCtrl.tickets | filter:tickets...". The key difference between these methods is how often they are run. The html template syntax filters are run on every digest cycle, the javascript filters are only run when the method is called, in your case, on each button click. For small apps or when the lists are small, this difference won't be noticeable, but if your app grows in size, the constant filtering on each digest cycle can cause page lag.
The filters in the controller are my preferred way of handling this, so I will show you how to clean up your code for these to work. You are almost there, just a few small changes are needed.
In your html, you can remove the inline filters in the ng-repeat, these aren't needed, and change the array to be your filter list, index.filteredTickets.
.html
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a ng-click="indexCtrl.clearfilter()">None</a></li>
<li><a ng-click="indexCtrl.openTickets()">Open</a></li>
<li><a ng-click="indexCtrl.closeTickets()">Close</a></li>
</ul>
<div ng-repeat="ticket in indexCtrl.filteredTickets">
<div class="ticket-no">
<h4>Ticket No:<span>{{ticket}}</span></h4>
</div>
<div class="ticket-title">
<a ng-href="/ticketView/{{ticket.ticketid}}"><h3>{{ticket.title}}</h3></a>
</div>
<div class="ticket-info">
<p class="pull-left">{{ticket.username}} On {{ticket.created | date:"MMM d, y h:mm a"}}</p>
<p class="pull-right">Status:<span>{{ticket.status}}</span></p>
</div>
<hr class="hr">
</div>
For the javascript, you need to make sure the filteredTickets are accessible in the html. I'm not sure if index == this, if not, you may need to attach the filtered tickets to the scope. The one other change needed is to set the filteredTickets to your original list if the none button is pressed. You will also want to call clearFilter after you load the list, otherwise index.filteredList will be undefined/null.
.js
this.UserTickets = () => {
//code to get the tickets
....
//after getting list, call clear filter
this.clearFilter();
}
this.openTickets = () => {
index.filteredTickets = $filter('filter')(index.tickets, { status: "open" } );
console.log(index.filteredTickets);
};
//filter closed tickets
this.closeTickets = () => {
index.filteredTickets = $filter('filter')(index.tickets, { status: "close" } );
console.log(index.filteredTickets);
};
this.clearFilter = () => {
//clear the filter
index.filteredTickets = index.tickets;
};
Related
I have a page with a flat structure where the order the elements appear on the page defines the structure I need to maintain when saving the data.
<div class='parent'>
<h3 class="'group">Furniture</h3>
<h4 class="category">Office</h4>
<ul>
<li>Desk</li>
<li>Chairs</li>
</ul>
<h4 class="category">Home</h4>
<ul>
<li>Couches</li>
<li>Tables</li>
<li>Bookshelves</li>
</ul>
<h4 class="category">Outdoor</h4>
<ul>
<li>Shade Umbrellas</li>
</ul>
</div>
<h3 class="'group">Toys</h3>
<h4 class="category">Toddlers</h4>
<h4 class="category">Kids</h4>
<ul>
<li>Balls</li>
<li>Dolls</li>
</ul>
</div>
There are enough good query selectors for me to pull everything I need from the page, but I can't figure out how to tell puppeteer to organize categories by groups and li elements by categories.
-- added on edit --
Since posting this, I have put all of the HTML from the parent div into a variable using:
let allHTML = await page.evaluate(() => {
return Array.from(document.querySelectorAll('.parent')).map(el => el.innerHTML);
})
I am now working on slicing up the allHTML variable to get blocks of text binned by group and category. Then, I can use these blocks to recreate the organizational structure on the page.
This should work, but it is cumbersome. I'm hoping there is a more straightforward way to have puppeteer retain the order of elements selected with different query selectors allowing me to determine which instances of .category (or <h4>) come after each instance of .group (or <h3>)element.
The question is not well defined, so I make some assumptions:
build a nested object from group ==> category => list items
use group class to determine the group
use category class to determine the category
a category belongs to the previous group sibling
a list belongs to the previous category sibling
Solution:
const html = `<h3 class="group">Furniture</h3> <h4 class="category">Office</h4> <ul> <li>Desk</li> <li>Chairs</li> </ul>
<h4 class="category">Home</h4> <ul> <li>Couches</li> <li>Tables</li> <li>Bookshelves</li> </ul> <h4 class="category">Outdoor</h4> <ul> <li>Shade Umbrellas</li> </ul> </div> <h3 class="'group">Toys</h3> <h4 class="category">Toddlers</h4> <h4 class="category">Kids</h4> <ul> <li>Balls</li> <li>Dolls</li> </ul>`;
const el = document.createElement("div");
el.innerHTML = html;
let result = {};
let groupVal = '';
let categoryVal = '';
Array.from(el.children).forEach(e => {
if(e.classList.contains('group')) {
groupVal = e.innerText;
result[groupVal] = {};
} else if(e.classList.contains('category')) {
categoryVal = e.innerText;
result[groupVal][categoryVal] = {};
} else if(e.tagName === 'UL') {
result[groupVal][categoryVal] = Array.from(e.children).map(li => li.innerText);
}
});
console.log(JSON.stringify(result, null, ' '));
Output:
{
"Furniture": {
"Office": [
"Desk",
"Chairs"
],
"Home": [
"Couches",
"Tables",
"Bookshelves"
],
"Outdoor": [
"Shade Umbrellas"
],
"Toddlers": {},
"Kids": [
"Balls",
"Dolls"
]
}
}
Note: Instead of using .createElement() on html text you could get the inner HTML from the DOM (here done just to demo the solution)
Sorry for the lengthy title, I have a feeling this is an oddly specific edge case not many people have to deal with.
Some background, I'm working on a webapp to track PC repairs for our shop. We have one currently, which we purchased and have access to the source code thanks to the author's method of distribution. Each repair is signified by a Work Order, all of which have notes. In the old app, the notes have the users name, the date it was posted, and the edit and delete buttons (if you are either the admin or the author) on the left, and the note on the right. If the user that posted the note changes when going down the list, it swaps them around, so the note is on the left and user is on the right, i.e.
user1 - note text
note text - user2
user3 - note text
user3 - note text
note text - user1
The old app did this with plain PHP, in a php file filled to the brim with echo statements. The new app I was working on has a laravel backend (to make use of things like Eloquent) with a Vue JS frontend (to assist with live updates in websockets). So on the Work Order page, there is a component for the list of notes, which takes the list of notes assigned to that Work Order as a prop, and iterates over all the notes with a v-for. I wanted to mimic the orientation switching feature from the previous setup. I can acheive the switch by setting up each note as a grid container, and applying either order-first or order-last to the column containing the user info. What I'm struggling with is trying to find a way to toggle which class is applied when the user changes.
At first I had a data attribute keeping track of the current class, so when the user changed I could check what the class was currently and switch it to the opposite. However, this caused an infinite render loop, as the entire list of notes would re-render whenever that attribute was changed. It did accomplish what I wanted to do visually, but it caused severe performance issues. Then I tried using refs, so when the user changed I could get the previous entry in the list from the refs array and examine its classes to see what order class it had to set the next elements order class appropriately. However this didn't work because the refs array would not be populated until the list was done rendering, and I needed to set the class as it rendered. I tried using a computed property, but it can't take arguments (i.e. the index of the array I was currently on to compare with index-1) and even if it could there is no way I could find to check the current cached value of that property while calculating the new one.
Here is the code I am working with for reference, currently with any of the previous approaches I tried removed, so currently no user switching happens.
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<note-form-modal :modal-id="'note'+note.noteid+'editModal'" :populate-with="note"></note-form-modal>
<div class="col-md-1 d-flex flex-column mx-md-3">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
<div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
<div class="btn-group justify-content-center p-0 m-0">
<template v-if="authusername === note.noteuser || authusername === 'admin'">
<button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
<button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
</template>
</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
import dateMixin from '../mixins/dateMixin'
export default {
mixins:[dateMixin],
props: ['initialnotes', 'authusername', 'noteType', 'woid'],
data () {
return {
notes: Object.values(this.initialnotes),
currentOrder: 'order-first',
newNote: {
notetype: this.noteType,
thenote: '',
noteuser: this.authusername,
woid: this.woid
}
}
},
mounted () {
Echo.channel('wonotes.'+this.noteType+'.'+this.woid)
.listen('WorkOrderNoteAdded', (e) => {
this.notes.push(e.note)
})
.listen('WorkOrderNoteEdited', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.note.noteid
})
this.notes[index] = e.note
})
.listen('WorkOrderNoteDeleted', (e) => {
let index = this.notes.findIndex((note) => {
return note.noteid === e.noteid
})
this.notes.splice(index, 1)
})
},
methods: {
createNote () {
axios.post('/api/workorders/notes', this.newNote)
.then((response) => {
$('#note'+this.noteType+'add').collapse('hide')
this.newNote.thenote = ''
})
}
}
}
</script>
noteType is there because we have two different types of notes, one that a customer can see, and one that only techs can see.
Is there something obvious I'm missing, have I just architected this thing wrong, am I trying to do something impossible? Any assistance would be greatly appreciated, I'm at the end of my rope here with this one.
I ended up figuring out a solution to this, not sure if its the best one but here goes.
Basically, I maintain an array parallel to the notes array that determines which order- class each array index should have. As it is dependent on the notes attribute, I make it a computed property, so that I can use the notes attribute and it automatically updates when the notes list changes. The file now looks like this (with some code related to posting new notes and editing existing ones removed for clarity)
<template>
<ul class="list-unstyled">
<li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
<div class="col-md-1 d-flex flex-column mx-md-3" :class="noteOrders[index]">
<div class="text-center p-0 m-0">{{note.noteuser}}</div>
</div>
<div class="col-md-10">
<div class="card m-2">
<div class="card-body p-2">
{{ note.thenote }}
</div>
</div>
</div>
</li>
</ul>
</template>
<script>
export default {
props: ['initialnotes'],
data () {
return {
notes: this.initialnotes,
}
},
computed: {
noteOrders () {
return this.getNoteOrders(this.notes)
}
},
methods: {
getNoteOrders(notes) {
let noteOrders = []
notes.forEach((note,index) =>{
if (index === 0) {
noteOrders[index] = 'order-first'
} else if (note.noteuser !== notes[index-1].noteuser) {
if (noteOrders[index-1] === 'order-first') {
noteOrders[index] = 'order-last'
} else {
noteOrders[index] = 'order-first'
}
} else {
noteOrders[index] = noteOrders[index-1]
}
})
return noteOrders
}
}
}
</script>
There may very well be better solutions and I encourage anyone who happens upon this to post theirs if they have one, but as I found a solution that works for me I decided to post it for anyone else running into the same issue.
I've set a value when the user click vote choice. Its working.
.then(function(response) {
localStorage.setItem('isClicked', 'Yes');
var i = +localStorage.key("nid");
var nidId = 'nid' + localStorage.length;
localStorage.setItem(nidId, vm.nid);
vm.clickedBefore = true;
})
This is my HTML scope:
<div class="card myfunc" ng-repeat="myfunc in myfuncPage.myfuncs" id="{{myfunc.nid}}" >
<div class="item item-text-wrap">
<h2 class="item-icon-left"><i class="icon ion-ios-analytics"></i>
{{myfunc.title}}</h2>
</div>
<div class="item item-text-wrap" ng-show="localstorage">
<img ng-src="{{myfunc.field_image.und[0].imgPath}}"/>
<p class="custom-class" ng-bind-html='myfunc.body.und[0].value'>
</p>
<ul ng-repeat="vote in myfunc.advmyfunc_choice">
<li ng-repeat="voteselect in vote">
<div class="row">
<button class="col button button-outline button-dark" ng-click="myfuncPage.sendNid(myfunc);myfuncPage.sendVote(voteselect);showVoted = ! showVoted" >{{voteselect.choice}}</button>
<div class="voted-screen" ng-show="showVoted">
<h3>Thanks.</h3>
</div>
</div>
</li>
</ul>
</div>
</div>
In basically, I need remember the div via localStorage and when the page refresh, hide choice divs.
ng-show="showVoted" working on click but I need on refresh.
What is the best way to do it? Thanks.
I am not sure what local storage module you are using, so I can't be specific, but I would write factory that handles the retrieval and storage of the values you need
(function () {
var voteFactory = function (localStorage) {
return {
getVote: function (key) {
var isVoted = false;
// get local storage item, set voted etc
var voted = localStorage.getItem(key);
if(voted) {
isVoted = voted;
}
return isVoted;
},
setVote: function(key, value) {
localStorage.setItem(key,value);
}
};
};
app.factory('voteFactory', ['localStorage', voteFactory]);
})();
Then within the scope controller/directive you are using to show or hide you would have a function:
$scope.showVoted = function(key) {
return voteFactory.getVote(key);
}
then ng-show="showVoted(theidentifieryouwantouse)"
It is also worthwhile to mention I would use ng-if instead of ng-show. To be more efficient you could store your votes as an array instead of individual values and do a check to see if you have retrieved all values, get from local storage if not. Then your functions would interrogate retrieved array instead of repeated calls to local storage. I would also advice maybe using a promise in the factory because retrieval from local storage could be delayed causing some ui oddities.
Hopefully this is along the lines of what you are looking for. I can elaborate if needed.
I have some Jquery tabs with Names on them.
(Ie: |Apple|Orange|Pear| )
I would like to update these titles to say something like
|Apple(2)|Orange|Pear(4)|
or something else based on the data returned by a php page ( say, numbers.php )
So,
Jquery requests numbers.php
numbers.php returns
Apple:2
Pear:4
How can I have JS, then use that data to dynamically update the Jquery tab "titles" with the data returned?
Then, if I re run the function, it would ask for numbers.php again and once again, update the tab titles based on what php returns?
Also, the names could change.. So next time it runs, Apples may not exist, but Pear may read 5 ..
In this instance, apple should be renamed "Apple"
Any help would be most excellent.
( hope ive made this clear? )
[ UPDATE ]
I have tried the below answer, but no luck.
The LI appears like this in the console:
<li id="tab-tabtestuser" class="ui-state-default ui-corner-top ui-tabs-active ui-state-active" role="tab" tabindex="0" aria-controls="newtab-tabtestuser" aria-labelledby="ui-id-5" aria-selected="true">tabtestuser <span class="count"></span><span class="ui-icon ui-icon-close" role="presentation">Remove Tab</span></li>
If you return the data from numbers.php in a meaningful format such as JSON, you can then process the data and update the tabs in Javascript much more easily. Here is a basic example of what you want to do, you will probably want to improve upon it.
numbers.php
{
apple: 5,
orange: 3,
pear: 2
}
Javascript:
$.getJSON( "numbers.php", function( data ) {
$.each( data, function( key, val ) {
$('#tab-' + key).find('span.count').text(val);
if (val == 0) {
$('#tab-' + key).hide();
};
});
});
HTML:
<div id="tabs">
<ul>
<li id="tab-apple">Apple <span class="count"></span></li>
<li id="tab-orange">Orange <span class="count"></span></li>
<li id="tab-pear">Pear <span class="count"></span></li>
</ul>
<div id="tabs-apple"></div>
<div id="tabs-orange"></div>
<div id="tabs-pear"></div>
</div>
I have two jqgrids on a single page. And those two are supposed to have context menus. It is possible if I have two different context menus, say 'myMenu1' and 'myMenu2'. But I would like to have only one context menu and i want to use it for both the grids and I actually took reference from the link from oleg. Please suggest how can i achieve??
<div class="contextMenu" id="myMenu1" style="display:none">
<ul style="width: 200px">
<li id="edit">
<span class="ui-icon ui-icon-pencil" style="float:left"></span>
<span style="font-size:11px; font-family:Verdana">Edit Row</span>
</li>
<li id="del">
<span class="ui-icon ui-icon-trash" style="float:left"></span>
<span style="font-size:11px; font-family:Verdana">Delete Row</span>
</li>
</ul>
</div>
And the binding i am doing is like the below.
loadComplete: function() {
$("tr.jqgrow", this).contextMenu('myMenu1', {
bindings: {
'edit': function(trigger) {
if (trigger.id && trigger.id !== lastSelection) {
grid_location.restoreRow(lastSelection);
grid_location.editRow(trigger.id, true);
lastSelection = trigger.id;
}
},
'del': function(trigger) {
if ($('#del').hasClass('ui-state-disabled') === false) {
// disabled item can do be choosed
if (trigger.id && trigger.id !== lastSelection) {
grid_location.restoreRow(lastSelection);
//grid.editRow(trigger.id, true);
//lastSelection = trigger.id;
}
grid_location.delGridRow(trigger.id, delSettings);
}
}
},
onContextMenu: function(event/*, menu*/) {
var rowId = $(event.target).closest("tr.jqgrow").attr("id");
//grid.setSelection(rowId);
return true;
}
});
}
I think that $(event.target).closest("table") is the reference to the current jqgrid, if there are no subgrids of course.
I solved this for local data, and I am posting the entire code over here. The only concern is that for the date picker is not picking the date if the columns of type date in the two grids have same names( or indexes). So for that I have used different names(inv_date and invdate) but in the case of server side data i am still confused.
onContextMenu: function(event/*, menu*/) {
/*
In this line we will be specifying on which grid the context menu has to perform
and this variable 'grid' has to be used in the 'onclickSubmitLocal' function.
*/
grid = $("#currnt_grid");
var rowId = $(event.target).closest("tr.jqgrow").attr("id");
return true;
}