Add class to DOM collection based on index value - javascript

var totalItems = $("#resultsList").children().length;
var itemsPerPage = 10;
var pages = Math.ceil(totalItems / itemsPerPage);
function createPagination(pages, page) {
var str = '<ul class="paginatorList">';
var active;
var pageCutLow = page - 1;
var pageCutHigh = page + 1;
var prevButton = '<li class=\'page-item previous\'><a onclick=\'createPagination(pages, ' + (page - 1) + ');\'><</a></li>';
var nextButton = '<li class="page-item next"><a onclick="createPagination(pages, ' + (page + 1) + ');">></a></li>';
var firstPage = '<li class="page-item page-item"><a onclick="createPagination(pages, 1);">1</a></li>';
var ellipseL = '<li class="page-item out-of-range"><a onclick="createPagination(pages, ' + (page - 2) + ');">...</a></li>';
var ellipseR = '<li class="page-item out-of-range"><a onclick="createPagination(pages, ' + (page + 2) + ');">...</a></li>';
// Show the Previous button only if you are on a page other than the first
if (page > 1) { str += prevButton; }
// Show all the pagination elements if there are less than 6 pages total
if (pages < 6) {
for (let p = 1; p <= pages; p++) {
active = page == p ? " active" : '';
var activePage = "<li class=\"page-item" + active + "\"><a onclick=\"createPagination(pages, " + p + ")\">" + p + "</a></li>";
str += activePage;
}
}
// Use "..." to collapse pages outside of 3 pages range
else {
// Show the very first page followed by a "..." at the beginning of the pagination section (after the Previous button)
if (page > 2) {
str += firstPage;
if (page > 3) {
str += ellipseL;
}
}
// Determine how many pages to show after the current page index
(page === 1) ? pageCutHigh += 2 : (page === 2) ? pageCutHigh += 1 : null;
// Determine how many pages to show before the current page index
(page === pages) ? pageCutLow -= 2 : (page === pages-1) ? pageCutLow -= 1 : null;
// Output the indexes for pages that fall inside the range of pageCutLow and pageCutHigh
for (let p = pageCutLow; p <= pageCutHigh; p++) {
if (p === 0) { p += 1; }
if (p > pages) { continue }
active = (page == p) ? " active" : '';
var activePage = "<li class=\"page-item" + active + "\"><a onclick=\"createPagination(pages, " + p + ")\">" + p + "</a></li>";
str += activePage;
}
// Show the very last page preceded by a "..." at the end of the pagination section (before the Next button)
if (page < pages-1) {
if (page < pages-2) {
str += ellipseR;
}
str += '<li class="page-item no"><a onclick="createPagination(pages, pages)">'+pages+'</a></li>';
}
}
// Show the Next button only if you are on a page other than the last
if (page < pages) { str += nextButton; }
str += '</ul>';
// Return the pagination string to be outputted
$("div[id^=pager]").html(str);
listFilter();
return str;
}
function listFilter() {
window.scrollTo(0,0);
function _toConsumableArray(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
arr2[i] = arr[i];
}
return arr2;
} else {
return Array.from(arr);
}
}
var list = document.querySelectorAll('.resultsItem');
var items = [].concat(_toConsumableArray(list));
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = (currentPgVal) * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
for (var item in items) {
if (item <= rangeMax && item >= rangeMin) {
var sortDir = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('#filterOptions').find(':selected').val();
tinysort('.resultsItem', { order: '' + sortDir, selector: '.' + sortOption });
items[item].classList.remove("hidden_apa");
}
}
}
// Sort when Icon clicked
$('#filterBtn i').on('click', function () {
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = (currentPgVal) * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
var sortDirection = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('.filterOptions').find(':selected').val();
// toggle displaying the two icons, A-Z or Z-A
$('i[class*=fa-sort-alpha]').toggleClass('hidden active');
// Hide all the items
$('.resultsItem').removeClass('active').addClass('hidden');
// Get the total list
var results = document.querySelectorAll('.resultsItem');
results.forEach(
function(val, i, listObj) {
console.log(i, val, this);
if(i >= rangeMin && i <= rangeMax) {
tinysort('.resultsItem', { order: '' + sortDirection, selector: '.' + sortOption });
// console.log(i, val);
$(this).removeClass('hidden');
}
});
});
$(document).ready(function () {
$("div[id^=pager]").html(createPagination(pages, 1));
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filterOptions">
<select name="filterMenu" class="searchFieldMenu">
<option value="firstName" data-sortby="first">First Name</option>
<option value="lastName" data-sortby="last">Last Name</option>
<option value="cityName" data-sortby="city">City</option>
<option value="stateName" data-sortby="state">State</option>
</select>
<div id="filterBtn">
<i class="fa fa-sort-alpha-asc active" data-sortdir="asc" aria-hidden="true"></i>
<i class="fa fa-sort-alpha-desc hidden_apa" data-sortdir="desc" aria-hidden="false"></i>
</div>
</div>
<ul id="resultsList">
<li class="resultsItem">
<div class="infoBox">
<h2 class="firstName">Zack</h2>
<h2 class="lastName">Last Name A</h2>
<div class="title">Manager</div>
<div class="company">Walmart</div>
<div class="location">
<span class="cityName">Juno</span>,
<span class="stateName">AK</span>
<br>United States</div>
</div>
</li>
</ul>
<div class="pagination" id="pager">
<ul class="paginatorList">
<li class="page-item active">
<a onclick="createPagination(pages, 1)">1</a>
</li>
<li class="page-item">
<a onclick="createPagination(pages, 2)">2</a>
</li>
</ul>
</div>
POST UPDATE
I've included all the relevant JS to allow the snippet to work. There will have to be many results added to the ul but I thought that was TOO Much code to have displayed.
I'm trying to sort a list of results after an icon is clicked. The expected result should be to hide all the elements in the UL and then unhide the results which meet the condition.
The condition is based on pagination and I'm using Tinysort to sort the list.
So for example, Say you are on page 1 of 3 and each page will only display 10 results. When you click on the Icon to sort the entire list A-Z then the results to be displayed are items whose index is within range of that page. So page 1's range is 1 to 10.
I'm close and I'm not able to remove the hidden class on the items that meet the criteria.
I hope I explained this well. I'll add my code so far.
the REAL problem I believe is in the NodeList or the .forEach Loop.

I had to slightly modify your code to make it work (before you posted the update).
Then I've made it to sort:
var itemsPerPage = 2;
// Sort when Icon clicked
$('#filterBtn i').on('click', function() {
var currentPgVal = parseInt(document.querySelector('.page-item.active a').textContent);
var rangeMax = currentPgVal * Number(itemsPerPage) - 1;
var rangeMin = (rangeMax - Number(itemsPerPage)) + 1;
var sortDirection = $('#filterBtn').find('i[class$=active]').data('sortdir');
var sortOption = $('.filterOptions').find(':selected').val();
// toggle displaying the two icons, A-Z or Z-A
$('i[class*="fa-sort-alpha"]').toggleClass('hidden_apa active');
var results = document.querySelectorAll('.resultsItem');
// sort
tinysort(results, {
order: sortDirection
})
// Hide out-of-range elements
.forEach(function(val, i) {
console.log(i, val.innerText);
$(val)[(
i < rangeMin || i > rangeMax ?
'add' : 'remove'
) + 'Class']('hidden_apa');
});
});
.hidden_apa {
display: none !important;
}
.active {
display: inline-block;
color: red
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filterBtn">
<i class="fa fa-sort-alpha-asc hidden_apa" data-sortdir="asc" aria-hidden="true"></i>
<i class="fa fa-sort-alpha-desc active" data-sortdir="desc" aria-hidden="false"></i>
</div>
<ul id="resultsList">
<li class="resultsItem">Item 1</li>
<li class="resultsItem">Item 2</li>
<li class="resultsItem hidden_apa">Item 3</li>
<li class="resultsItem hidden_apa">Item 4</li>
<li class="resultsItem hidden_apa">Item 5</li>
<li class="resultsItem hidden_apa">Item 6</li>
</ul>
<div class="pagination" id="pager">
<span class="page-item active"><a>1</a></span>
<span class="page-item"><a>2</a></span>
<span class="page-item"><a>3</a></span>
</div>

Related

HTML and Javascript search result pagination - limit the number of result pages showing

Using HTML and javascript I have some basic search result pagination which, although works, if there are hundreds of results, I can end up with pagination looking like this:
I would like to limit this to show only 1 - 10, then 11 - 10 etc, can anyone direct me to an example of how to do this?
<div class="row igs-learning-paths-pagination-row">
<div class="col-12 d-flex justify-content-center" >
<ul class="pagination pagination-info">
<li class="page-item">
<a href="javascript:void(0)" onclick="displayPreviousPage()" class="igs-learning-paths-pagingation-text igs-text-uppercase">
#Umbraco.GetDictionaryValue("Common Prev", "Prev..").ToUpper()
</a>
</li>
#for (int i = 0; i <= #Model.Results.Count() / numberPerPage; i++)
{
<li class="#(i == 0 ? "active" : null) page-item non-generate-page-item" id="page-list-item-#(i)" style="border-radius:16px;">
<a class="page-link" id="page-number-#(i)" href="javascript:void(0)" onclick="displayPages(#(i))">#(i + 1)</a>
</li>
}
<li class="page-item" alt="Go forward a page" title="Go forward a page">
<a href="javascript:void(0)" alt="Go forward a page" title="Go forward a page"
onclick="displayNextPage()"
class="igs-learning-paths-pagingation-text igs-text-uppercase search-result-margin">
#Current.UmbracoHelper.GetDictionaryValue("Common Next", "Next..").ToUpper()
</a>
</li>
</ul>
</div>
</div>
<script>
var numberPerPage = #numberPerPage;
var totalResults = #Model.Results.Count();
var maxPage = Math.floor(totalResults / numberPerPage);
var currentPage = 0;
var previousPage = 0;
function displayNextPage() {
if (currentPage == maxPage) return;
currentPage = currentPage + 1
displayPages(currentPage);
}
function displayPreviousPage() {
if (currentPage == 0) return;
currentPage = currentPage - 1
displayPages(currentPage);
}
function displayPages(pageToDisplay) {
$(".page-search-hider").hide();
currentPage = pageToDisplay;
if (!pageToDisplay) {
pageToDisplay = 0
}
else {
var skip = pageToDisplay * numberPerPage;
pageToDisplay = skip+1;
}
var newNumberPerPage = numberPerPage;
document.getElementById("page-list-item-" + previousPage).classList.remove("active")
previousPage = currentPage;
document.getElementById("page-list-item-" + currentPage).classList.add("active")
for (var i = 0; i <= newNumberPerPage; i++) {
var result = pageToDisplay + i;
$("#pageId-" + result).show();
$("#pageId-" + #(pageIdCount)).hide();
}
}
const pageNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const maxPageLimit = 5;
let upperPageIndex = maxPageLimit;
let currentPageIndex = 0;
let displayPages = pageNumbers.slice(currentPageIndex , upperPageIndex); // slice(startIndex, endIndex);
console.log(displayPages)
// if currentPageIndex = 2 then show 2 more as you go through
upperPageIndex += 2; // You can make 2 a constant if needed
currentPageIndex += 2;
displayPages = pageNumbers.slice(currentPageIndex , upperPageIndex);
console.log(displayPages)
REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

Limiting Pagination using Javascript

I have a web app that uses OMDB API which fetches all the related results that matches the title of a Movie searched by the user.
So for example the user searched for "Star Wars" the API will then return 483 results, I managed to make a pagination for it but it shows all the pages from 1-48 and what I'm trying to figure out is how can I only show pages [1,2,3,4,5,6,7...49(ending page)], then change that pagination to [2,3,4,5,6,7,8...49] and so on.
Heres the code for it:
<input type="text" class="form-control" id="title" name="title" placeholder="Movie Title...">
<button onclick="callOMDB(document.getElementById('title').value)" class="btn btn-primary">Search</button>
<div id="page" class="page">
<nav aria-label="Page navigation example">
<ul class="pagination" id="pagination">
</ul>
</nav>
</div>
<div id="info">
</div>
function callOMDB(x){
var poster = document.getElementById("info");
var page = document.getElementById("pagination");
var search = x;
var searchLink = 'https://www.omdbapi.com/?i=tt3896198&apikey=123456&s='+encodeURI(x);
$.getJSON(searchLink).then(function(response){
poster.innerHTML = '';
page.innerHTML = '';
var length = response.Search.length;
for(var i = 0; i < length; i++){
var yr = response.Search[i].Year;
var title = response.Search[i].Title;
poster.innerHTML += "<p>Year: "+yr+"</p><p>Title: "+title+"</p>";
}
var pageNo = response.totalResults/10;
var i = 0;
for(i = 1; i < pageNo; i++){
page.innerHTML += '<li class="page-item"><a onclick="nextPage('+i+',\''+search+'\')" class="page-link" href="#">'+i+'</a></li>';
}
});
}
You can use slice to get slice of the entire result and display that. For the purpose of demo I just shown you only. and results are just numbers. The array can be anything. Important things is slice to get parts of results you want and display them.
After as you click through you move the start and end positions
const resultsList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // This could be results / or page numbers
const maxResults = 9;
let upperPageIndex = maxResults;
let currentPageIndex = 0;
let resultsToDisplay = resultsList.slice(currentPageIndex , upperPageIndex); // slice(startIndex, endIndex);
console.log(resultsToDisplay)
// if currentPageIndex = 2 then show 2 more as you go through
upperPageIndex += 1; // You can make 2 a constant if needed
currentPageIndex += 1;
resultsToDisplay = resultsList.slice(currentPageIndex , upperPageIndex);
console.log(resultsToDisplay)
I used the paramter page in the omdbapi and that is my code
JS
window.addEventListener('DOMContentLoaded', function () {
const posterContainer = document.getElementById("info");
const pageContainer = document.getElementById("pagination");
const titleInput = document.getElementById('title');
const searchBtn = document.getElementById('searchBtn');
const searchLink = 'https://www.omdbapi.com/?i=tt3896198&apikey=56fbcd03';
searchBtn.addEventListener('click', () => {
getData(titleInput.value);
});
// event delegation
$('body').on('click', '.page-link', function (e) {
e.preventDefault();
getData(titleInput.value, this.id);
});
function getData(keyword, page = 1) {
$.getJSON(`${searchLink}&page=${page}&s=${keyword}`).then(function (response) {
posterContainer.innerHTML = '';
pageContainer.innerHTML = '';
var length = response.Search.length;
for (var i = 0; i < length; i++) {
var yr = response.Search[i].Year;
var title = response.Search[i].Title;
posterContainer.innerHTML += "<p>Year: " + yr + "</p><p>Title: " + title + "</p>";
}
var pageNo = response.totalResults / 10;
var i = 0;
for (i = 1; i < pageNo; i++) {
pageContainer.innerHTML += `<li class="page-item" ><a class="page-link" id="${i}" href="#">${i}</a></li>`;
}
});
}
});
HTML
<input type="text" class="form-control" id="title" name="title" placeholder="Movie Title...">
<button id="searchBtn" class="btn btn-primary">Search</button>
<div id="page" class="page">
<nav aria-label="Page navigation example">
<ul class="pagination" id="pagination">
</ul>
</nav>
</div>
<div id="info">
</div>

Javascript creating json object from two strings

So I will start with my needs. I have a task to create json output using nightwatch.js from the ul list where inside lists are few div elements with classes like name, surname... But really I can't think of any of solutions. Here is my html
<html>
<meta charset="utf-8">
<body>
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>
</body>
</html>
And here is my nightwatch.js script
'Test' : function(browser) {
function iterate(elements) {
elements.value.forEach(function(el) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
// output for json i guess
console.log(att.value + ' => ' + r.value)
})
});
});
}
browser
.url('http://url.com/nightwatch.php')
.waitForElementVisible('body', 8000)
.elements('css selector', 'ul li div', iterate)
.end();
}
Basically this will execute the following:
name => John
surname => Lewis
name => Nick
surname => Kyrgios
Output is a string for both...
And how can I make it like
[{name: "John", surname: "Lewis"}, {name: "Nick", surname: "Kyrgios"}]
This should work. You just need to keep track of the object and place it inside the array after list.
function iterate(elements) {
var objArr = [];
var obj = {};
elements.value.forEach(function(el, idx) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
if (obj.hasOwnProperty(att.value)) {
objArr.push(obj);
obj = {};
}
obj[att.value] = r.value;
});
});
if (idx === (elements.value.length-1)) {
objArr.push(obj);
console.log(objArr);
}
});
}
As with Will's solution, I used straight JavaScript. It does not appear that the nightwatch.js code for this provides any significant benefit. In addition, your question does not specify that only nightwatch.js should be used.
As opposed to Will, I have assumed that the class on your inner <div> elements could be arbitrary and that the arbitrary class should be used as the key/property on the object for that entry. Choosing to use this method vs. restricting it only to a name or surname property will depend on what your HTML really is, and how you want to handle classes which are not those two strings.
var theList = [];
var listItems = document.querySelectorAll('li');
for (var itemIndex=0,itemLength=listItems.length; itemIndex < itemLength; itemIndex++) {
var entry = {};
divs = listItems[itemIndex].querySelectorAll('div');
for (var divsIndex=0, divsLength=divs.length; divsIndex < divsLength; divsIndex++) {
entry[divs[divsIndex].className] = divs[divsIndex].textContent;
}
theList.push(entry);
}
outputJson = JSON.stringify(theList);
console.log(outputJson);
<html>
<meta charset="utf-8">
<body>
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>
</body>
</html>
What about something like this?
function iterate(elements) {
var jsonArray = [];
var jsonBuffer = "";
elements.value.forEach(function(el) {
browser.elementIdText(el.ELEMENT, function(r) {
browser.elementIdAttribute(el.ELEMENT, 'class', function(att){
// output for json i guess
if (att.value == 'name') {
jsonBuffer += "{" + att.value + ":" + "" + r.value + "" + ",";
}
else {
jsonBuffer += att.value + ":" + "" + r.value + "" + "}";
jsonArray.push(jsonBuffer);
jsonBuffer = "";
}
})
});
});
var jsonOutput = "[";
var i = 0;
jsonArray.forEach(function(el) {
if (i < jsonArray.length) {
jsonOutput += el + ",";
} else {
jsonOutput += el + "]";
}
i++;
}
}
I'm not familiar with Nightwatch, but you essentially loop through the elements and push them on to an array.
var results = [];
var entries = document.querySelectorAll('li');
for (var ix = 0; ix < entries.length; ix++) {
var name = entries[ix].querySelector('.name').innerText;
var surname = entries[ix].querySelector('.surname').innerText;
results.push({
name: name,
surname: surname
});
}
console.log(results);
<ul class="random">
<li class="list">
<div class="name">John</div>
<div class="surname">Lewis</div>
</li>
<li class="list odd">
<div class="name">Nick</div>
<div class="surname">Kyrgios</div>
</li>
</ul>

How to make the what happening box like twitter in our site?

I am doing this by taking the cursor position from the content-editable box. When a new tag is created the cursor comes before the tag but it should be after the tag. Also i am not able to merge/split the tag.
Please give some idea how can i do this.
Visit (https://plnkr.co/edit/DSHKEcOnBXi54KyiMpaT?p=preview) !
What i want here, after pressing the enter key for new tag the cursor should be at the end of tag while it is not and also the merging/spliting functionality like the twitter what's happening box.
Thanks in advance.
Now this code is working fr me
$scope.myIndexValue = "5";
$scope.searchTag = function(term) {
var tagList = [];
angular.forEach($rootScope.tags, function(item) {
if (item.name.toUpperCase().indexOf(term.toUpperCase()) >= 0) {
tagList.push(item);
}
});
$scope.tag = tagList;
return $q.when(tagList);
};
$scope.getTagText = function(item) {
// note item.label is sent when the typedText wasn't found
return '<a>#<i>' + (item.name || item.label) + '</i></a> ';
};
$scope.resetDemo = function() {
// finally enter content that will raise a menu after everything is set up
$timeout(function() {
//var html = "Tell us something about this or add a macro like brb, omw, (smile)";
var htmlContent = $element.find('#htmlContent');
var html = "";
if (htmlContent) {
var ngHtmlContent = angular.element(htmlContent);
ngHtmlContent.html(html);
ngHtmlContent.scope().htmlContent = html;
// select right after the #
mentioUtil.selectElement(null, htmlContent, [0], 8);
ngHtmlContent.scope().$apply();
}
}, 0);
};
HTML :
<div class="share_tags fs-12">
<div class="row margin_row">
<div class="col-md-12 no_padding">
<div class="form-group">
<div contenteditable="true" mentio
mentio-typed-term="typedTerm"
mentio-macros="macros"
mentio-require-leading-space="true"
mentio-select-not-found="true"
class="editor tag" placeholder="Tell Us something about This"
mentio-id="'htmlContent'"
id="htmlContent"
ng-model="htmlContent">
</div>
</div>
<mentio-menu
mentio-for="'htmlContent'"
mentio-trigger-char="'#'"
mentio-items="tag"
mentio-template-url="/people-mentions.tpl"
mentio-search="searchTag(term)"
mentio-select="getTagText(item)"
></mentio-menu>
</div>
</div>
<script type="text/ng-template" id="/people-mentions.tpl">
<ul class="list-group user-search">
<li mentio-menu-item="tag" ng-repeat="tag in items" class="list-group-item">
<span ng-bind-html="tag.name | mentioHighlight:typedTerm:'menu-highlighted' | unsafe"></span>
</li>
</ul>
</script>
</div>
Reference link
http://jeff-collins.github.io/ment.io/?utm_source=angular-js.in&utm_medium=website&utm_campaign=content-curation#/
is working fine for me.
This is not working perfectly but for the time being i am using this code.
In app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function ($scope, $filter, $element) {
var tags;
$scope.allTags = ['Tag1', 'PrivateTag', 'Xtag', 'PublicTag1', 'newTag', 'socialTag', 'cricketTag'];
var replacedTag = '';
var replacedIndex;
var data;
$scope.log = function (name) {
$scope.tags = [];
$('ul').html(' ');
console.log("here", $('ul'))
var data = $('textarea').val();
replacedIndex = data.indexOf(replacedTag)
console.log('test', name, replacedTag, replacedIndex, data);
var replacedData = data.substring(0, replacedIndex - 1) + ' #' + name + data.substr(replacedIndex + replacedTag.length);
$('textarea').val(replacedData);
$('textarea').keyup();
}
f = $scope.log;
$('textarea').on('keyup', function (e) {
function getIndexOf(arr, val) {
var l = arr.length,
k = 0;
for (k = 0; k < l; k = k + 1) {
if (arr[k] === val) {
return k;
}
}
return false;
}
$('ul').html('');
$scope.tags = [];
tags = $(this).val().match(/#\S+/g);
console.log("---tags-", tags)
var a = data = $(this).val();
if (tags && tags.length) {
tags.forEach(function (tag,index) {
var index1 = getIndexOf(tags, tag);
console.log("index----",index, index1,tag)
replacedTag = tag;
$scope.tags = tag ? $filter('filter')($scope.allTags, tag.substr(1)) : [];
if ($scope.tags && $scope.tags.length && (e.keyCode && e.keCode != 32)) {
$scope.tags.forEach(function (tag1, index) {
$('ul').append('<li>' + '<a href="javascript:;" onclick=f("' + tag1 + '");>'
+ tag1 + '</a>' + '</li>')
})
}
else {
$('ul').html(' ');
}
if(index == index1) {
var b = a.substring(0, a.indexOf(tag) - 1) + ' <a>' + tag + '</a> ' + a.substr(a.indexOf(tag) + tag.length);
}
else {
var b = a.substring(0, a.lastIndexOf(tag) - 1) + ' <a>' + tag + '</a> ' + a.substr(a.lastIndexOf(tag) + tag.length);
}
a = b;
$('p').html(b)
})
}
})
});
HTML
<br>
<br>
<p></p>
<textarea rows="2" cols="80"></textarea>
<div>
<ul>
</ul>
</div>
For live demo Visit
https://plnkr.co/edit/SD9eouQa5yrViwxQD6yN?p=preview
i am also looking for the better answer.
I assume you're talking about gathering hash tags from a string of sorts, the snippet below demonstrates how you can build an array of #hashed tags without modifying the cursor position.
It uses a simple regular expression to match tags found in the textarea and then pushes them to an array.
var tags;
$('textarea').on('keyup', function(){
tags = $(this).val().match(/#\S+/g)
$('ul').html('');
tags.forEach(function(tag){
$('ul').append('<li>' + tag + '</li>')
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
<ul></ul>

KnockoutJS or Javascript not keeping proper track of displayed array

I'm writing a paginated table with a page selector at the bottom that displays the different page numbers
I'm using knockout. The numbers are coming from a ko.computed array (self.pages) that calculates how many pages there are based on the number of results / results per page. The problem I'm running into is if the data array is very long and the results per page is set somewhat low, I get something like this:
What I want to do is limit the number of menu items to three, so if page #4 is selected, only items 3,4,5 are visible. Currently I'm implementing a second ko.computed that first retrieves the value of self.pages, then gets the value of the current page number (self.pageNumber), and slices the array so that only 3 items are returned:
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
Now all of this seems to be working fine but there's one bug I have not been able to stamp out. Using the knockout css data-bind, I'm telling it to assign a class of 'selected' to whichever element holds the same value as self.pageNumber (see code below).
If the element selected does not require self.availablePages to change (i.e. selecting 2 when 1 was the previous selection), there are no problems; 2 becomes selected and 1 becomes un-selected.
However, if the selection does require self.availablePages to change (i.e. 1,2,3 visible, selecting 3 will change visible to 2,3,4), the correct numbers display, but instead of 3 being selected, 4 is selected. I'm assuming this is because the index of the array that 3 used to be located at (last) is now being occupied by 4.
Here's the menu:
<ul data-bind="foreach: availablePages">
<li data-bind="if: $index() < 1">
<a data-bind="click: $parent.toFirstPage">First</a>
</li>
<li>
<a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: $parent.pageNumber() === iterator }"></a>
</li>
<li data-bind="if: $parent.isLastIteration($index)">
<a data-bind="click: $parent.toLastPage">Last</a>
</li>
</ul>
The array being iterated over was originally just an array of numbers, but in trying to fix this bug I changed it to be an array of the following object:
available.MenuModel = function(iterator) {
var self = this;
self.displayValue = iterator + 1;
self.iterator = iterator;
self.isSelected = ko.observable(false);
}
One thing I tried doing was adding the self.isSelected observable to all items in the menu, and then when self.availablePages gets re-computed, the function checks what the pageNumber is and then finds which item in the array matches that and sets self.isSelected(true), and then tried keying the css binding to that.
Unfortunately this did not work; it still has the exact same bug. I've been debugging the script like crazy and there doesn't seem to be an issue; everything seems to know that 3 should be selected, but what's actually selected is 4.
I'm guessing that the knockout bindings aren't smart enough to keep up with this. Is there something I can do or some pattern that would help knockout keep track of which element should be selected? I even tried taking knockout out of it completely, and had a function in the script manually remove/add the 'selected' class whenever self.pageNumber was changed and/or whenever self.availablePages changed but I still got the same issue, so maybe this isn't a knockout issue but something with javascript.
I've tried everything else I can think of; subscribing to various observables, promises, but like I said everything already knows what should be selected so additional checks and callbacks aren't altering anything nor eliminating the bug.
I'm hoping someone will either know the cause/solution of the bug or a smarter way to accomplish the task. This is the self.pages that self.availablePages keys off of, in case that's helpful:
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
This is the entire javascript model (using requireJs):
define(['underscore', 'knockout'], function(_, ko) {
var available = available || {};
available.DynamicResponsiveModel = function(isDataObservable, isPaginated) {
var self = this;
self.workingArray = ko.observableArray([]);
self.backgroundArray = ko.observableArray([]);
self.pageNumber = ko.observable(0);
self.count = function () {
return 15;
}
self.resultsPerPage = ko.observable(self.count());
self.selectResultsPerPage = [25, 50, 100, 200, 500];
self.resultsPerPageOptions = ko.computed(function () {
return self.selectResultsPerPage;
});
self.activeSortFunction = isDataObservable ? available.sortAlphaNumericObservable : available.sortAlphaNumeric;
self.resetPageNumber = function() {
self.pageNumber(0);
}
self.initialize = function(data) {
var sortedList = data.sort(function(obj1, obj2) {
return obj2.NumberOfServices - obj1.NumberOfServices;
});
self.workingArray(sortedList);
self.backgroundArray(sortedList);
self.pageNumber(0);
}
self.intializeWithoutSort = function(data) {
self.workingArray(data);
self.backgroundArray(data);
self.pageNumber(0);
}
self.totalPages = ko.computed(function() {
var num = Math.floor(self.workingArray().length / self.resultsPerPage());
num += self.workingArray().length % self.resultsPerPage() > 0 ? 1 : 0;
return num - 1;
});
self.paginated = ko.computed(function () {
if (isPaginated) {
var first = self.pageNumber() * self.resultsPerPage();
return self.workingArray.slice(first, first + self.resultsPerPage());
} else {
return self.workingArray();
}
});
self.pages = ko.computed(function() {
var start = self.totalPages();
var pages = [];
for (var i = 0; i < start + 1; ++i)
pages.push(new available.MenuModel(i));
return pages;
});
self.availablePages = ko.computed(function() {
var pages = self.pages();
var current = self.pageNumber();
if (current === 0) {
return pages.slice(current, current + 3);
} else {
return pages.slice(current - 1, current + 2);
}
});
self.pageNumDisplay = ko.computed(function() {
return self.pageNumber() + 1;
});
self.hasPrevious = ko.computed(function() {
return self.pageNumber() !== 0;
});
self.hasNext = ko.computed(function() {
return self.pageNumber() !== self.totalPages();
});
self.next = function() {
if (self.pageNumber() < self.totalPages()) {
self.pageNumber(self.pageNumber() + 1);
}
}
self.previous = function() {
if (self.pageNumber() != 0) {
self.pageNumber(self.pageNumber() - 1);
}
}
self.toFirstPage = function() {
self.pageNumber(0);
}
self.toLastPage = function() {
self.pageNumber(self.totalPages());
}
self.setPage = function(data) {
return new Promise(function(resolve, reject) {
self.pageNumber(data);
});
}
self.goToPage = function(data) {
self.pageNumber(data);
}
self.isLastIteration = function (index) {
var currentIndex = index();
var count = self.pages().length;
return currentIndex === count - 1;
}
self.resultsPerPage.subscribe(function() {
self.pageNumber(0);
});
self.filterResults = function (filterFunction) {
self.resetPageNumber();
self.workingArray(filterFunction(self.backgroundArray()));
}
self.resetDisplayData = function() {
self.workingArray(self.backgroundArray());
}
self.updateVisibleResults = function(data) {
self.workingArray(data);
}
}
available.sortAlphaNumericObservable = function () {
//...
}
available.sortAlphaNumeric = function () {
//...
}
return available;
});
Here's the entire table:
<div data-bind="visible: showListOfEquipment, with: availableEquipmentModel">
<section class="panel panel-default table-dynamic">
<table class="primary-table table-bordered">
<thead>
<tr>
<th>
<div class="th">
Part Number
<span class="fa fa-angle-up" data-bind="click: function () { sortByFirstColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFirstColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Serial Number
<span class="fa fa-angle-up" data-bind="click: function () { sortBySecondColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySecondColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Type
<span class="fa fa-angle-up" data-bind="click: function () { sortByThirdColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByThirdColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Equipment Group
<span class="fa fa-angle-up" data-bind="click: function () { sortByFourthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFourthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Operational
<span class="fa fa-angle-up" data-bind="click: function () { sortByFifthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortByFifthColumn(true); }"></span>
</div>
</th>
<th>
<div class="th">
Valid
<span class="fa fa-angle-up" data-bind="click: function () { sortBySixthColumn(false); }"></span>
<span class="fa fa-angle-down" data-bind="click: function () { sortBySixthColumn(true); }"></span>
</div>
</th>
</tr>
</thead>
<tbody data-bind="foreach: paginated">
<tr>
<td data-bind="text: $data.PartNumber"></td>
<td><a target="_blank" data-bind="text: $data.SerialNumber, click: function () { $root.setSerialNumberAndFindEquipment(SerialNumber) }" style="color:royalblue"></a></td>
<td data-bind="text: $data.Type"></td>
<td data-bind="text: $data.EquipmentGroup"></td>
<td>
<span data-bind="css: $root.operationalCss($data), text: $root.getOpStatus($data)"></span>
</td>
<td data-bind="text: $data.Validity"></td>
</tr>
</tbody>
</table>
<footer class="table-footer">
<div class="row">
<div class="col-md-6 page-num-info">
<span>Show <select style="min-width: 40px; max-width: 50px;" data-bind="options: selectResultsPerPage, value: resultsPerPage"></select> entries per page</span>
</div>
<div class="col-md-6 text-right pagination-container">
<ul class="pagination-sm pagination" data-bind="foreach: pages">
<li data-bind="if: $index() < 1"><a data-bind="click: $parent.toFirstPage">First</a> </li>
<li class="paginationLi"><a data-bind="text: displayValue, click: $parent.goToPage(iterator), css: { selected: isSelected }"></a></li>
<li data-bind="if: $parent.isLastIteration($index)"> <a data-bind="click: $parent.toLastPage">Last</a> </li>
</ul>
</div>
</div>
</footer>
</section>
I went ahead and built a paginator. Instead of using an array as you did, I used just the number of available pages, pageCount.
Probably the only thing worth looking into in more detail is the calculation which pages are to be displayed:
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
// too close to the beginning
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
// too close to the end
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
// just right
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
Let's go through this piece by piece. We want our current page to be in the middle of all displayed pagination buttons, with some to its left and some to its right. But how many?
If we use an odd number such as three, that's simple: the number minus 1 (the selected one) divided by two. (3 - 1) / 2 = 1, or one to each side.
With an even number of pagination buttons to display, that doesn't work, so we calculate each side individually and round one result up and one result down:
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
There are three possible results:
our selection fits
we're too close to the beginning
we're too close to the end
If we're too close to the beginning:
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
}
we start with 1 and try to display pages 1 up to visiblePageCount. If that doesn't work either, because we don't have enough pages, we simply display all we have.
If we're too close to the end:
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
}
we end with the last page and try to display as many as we need to the left. If that doesn't work, because we don't have enough pages, we simply display all we have.
Here's the full example:
var ViewModel;
ViewModel = function ViewModel() {
var that = this;
this.pageCount = ko.observable(20);
this.currentPage = ko.observable(1);
this.visiblePageCount = ko.observable(3);
this.gotoPage = function gotoPage(page) {
that.currentPage(page);
};
this.visiblePages = ko.computed(function() {
var previousHalf = Math.floor( (this.visiblePageCount() - 1) / 2 ),
nextHalf = Math.ceil( (this.visiblePageCount() - 1) / 2 ),
visiblePages = [],
firstPage,
lastPage;
if ( this.currentPage() - previousHalf < 1 ) {
firstPage = 1;
lastPage = this.visiblePageCount();
if ( lastPage > this.pageCount() ) {
lastPage = this.pageCount();
}
} else if ( this.currentPage() + nextHalf > this.pageCount() ) {
lastPage = this.pageCount();
firstPage = this.pageCount() - this.visiblePageCount() + 1;
if (firstPage < 1) {
firstPage = 1;
}
} else {
firstPage = this.currentPage() - previousHalf;
lastPage = this.currentPage() + nextHalf;
}
for (var i = firstPage; i <= lastPage; i++) {
visiblePages.push(i);
}
return visiblePages;
}, this);
};
ko.applyBindings( new ViewModel() );
ul {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
margin: 0;
padding: 0;
list-style-type: none;
}
ul li {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
button {
margin-right: 0.5rem;
padding: 0.5rem;
background-color: lightgrey;
border: none;
}
button.selected {
background-color: lightblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul>
<li><button data-bind="click: gotoPage.bind($data, 1)">First</button></li>
<!-- ko foreach: visiblePages -->
<li>
<button data-bind="text: $data,
click: $parent.gotoPage,
css: { selected: $parent.currentPage() === $data }"></button>
</li>
<!-- /ko -->
<li><button data-bind="click: gotoPage.bind($data, pageCount())">Last</button></li>
</ul>

Categories