I have a very basic search function that sorts through a list of names. There are about 4,000 names so this loop is a bit to slow to keep up as users type. Here is the loop for each new search value:
elements.each(function(i, el){
var name = $(el).find('button').text();
name = name.toLowerCase();
if(name.indexOf(value) >= 0) {
$(el).show().addClass('visible');
}
else {
$(el).hide().removeClass('visible');
}
});
How can I optimize this loop so it can keep up with a user typing?
Follow up:
Based on user suggestions, I mapped and stored the data in an array when the text input receives focus. Then, I changed the active search to the below:
Array.prototype.forEach.call(this.cache, function(el, i){
var name = el.name;
if(name.indexOf(value) >= 0) {
el.element.style.display = '';
el.element.classList.add('visible');
}
else {
el.element.style.display = 'none';
el.element.classList.remove('visible');
}
});
I tried to cut off jQuery where I could to better optimize. It seems to be working without any noticeable lag now! Thanks for the great suggestions and discussion.
As mentioned #Taplar, you could delay the error-check.
Not everything has to be real-time, as it could harm the usability.
It's common to check something in a timespan after the the system stops receiving the action, and while user is typing, you could just grey out the status text and once 500ms after user stops typing passes, you execute the check and assign necessary class to the status text.
Here's an example implementation. It won't throw a "error" unless you type "BAD" in the text box.
var timeout;
$("#txt").keydown(function(e){
$("#status").removeClass("bad").addClass("wait");
clearInterval(timeout);
timeout = setTimeout(checkStuff, 500);
});
function checkStuff(){
var t = $("#txt").val();
var s = $("#status");
s.removeClass("wait").html("No errors");
if(t.indexOf("BAD") > -1) {
s.addClass("bad").html("Errors detected!");
}
}
#status{
color:green;
}
#status.wait{
color:rgba(150,150,150,1);
}
#status.bad{
color:red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="txt" placeholder="type plz"><br>
<span id="status">No errors</span>
jQuery is sloooooooooooooooow, if you can do your looping and most of your heavily repeated functionality in vanilla JS, you will save a lot of processing time. Also pre-mapping text is huge, looking up the button element and getting the text value every time is a big waste.
/*
elements.each(function(i, el){
var name = $(el).find('button').text();
name = name.toLowerCase();
if(name.indexOf(value) >= 0) {
$(el).show().addClass('visible');
}
else {
$(el).hide().removeClass('visible');
}
});
*/
let elemQueryArr = elements.map(elem => {
return {
elem: elem,
text: elem.querySelector('button').text.toLowerCase()
}
}), len = elemQueryArr.length
function filterElems(val){
let i = -1
while(++i < len){
let elem = elemQueryArr[i]
if(elem.text.indexOf(val) > -1) elem.elem.classList.add('visible')
else elem.elem.classList.remove('visible')
}
}
Related
It is additional question of How to stop a loop when clicking asynchronously in CasperJS
I tried this code
function execOnce(casper, i, max){
// end condition
if (i === max) {
return;
}
casper.wait(3000, function() {
var button = x('//*[#id="content"]/div[3]/a['+i+']');
if (!this.exists(button)) {
this.echo(i + " not available");
return;
}
this.thenClick(button, function (){
console.log('Searching dic');
words = words.concat(this.evaluate(getWords));
// recursive step
execOnce(this, i+1, max);
});
});
};
// start the recursive chain
casper.then(function(){
execOnce(this, 1, 200);
});
But I found that indexes' Xpath of my target web pages has iteration.
When it reached '//*[#id="mArticle"]/div[2]/a['11']' next index's Xpath becomes '//*[#id="mArticle"]/div[2]/a['2'] (back to a['2'])
for example the webpage url is "http://krdic.naver.com/search.nhn?query=%E3%85%8F%E3%85%8F&kind=keyword"
under the page there are [1][2][3][4][5][6][7][8][9][10] [Next Page]
When I click Next page you can see
[Previous Page][11][12][13][14][15][16][17][18][19][20] [Next Page]
but [12] 's Xpath is not //*[#id="content"]/div[3]/a[12] ---> It is
//*[#id="content"]/div[3]/a[2]
So I have to do iteration of function execOnce including code casper.wait(6000, function() {}
because my target website is really sensitive to query so I put "wait" code whenever I can..!
In case of this can I use nested function like this?
function execOnce(casper, i, max){
if (i === max) {
function execOnce(casper, i, max){
return;
}
...
XPath is very expressive. You can for example select the intended page link based on link text instead of link position (//div[#class='paginate']/a[text()='5']), but that alone doesn't help you much in this case.
The problem is of course that the site has a secondary pagination. You need to go to the next pagination page, before you can click of the next pagination links.
casper.wait(3000, function() {
var nextButton = x('//*[#id="content"]/div[3]/a[text()="'+i+'"]');
var lastPageNextButton = '.paginate > strong + a.next';
var button = nextButton;
if (this.exists(lastPageNextButton)) {
button = lastPageNextButton;
} else if (!this.exists(button)) {
this.echo(i + " not available");
return;
}
this.thenClick(button, function (){
console.log('Searching dic');
words = words.concat(this.evaluate(getWords));
// recursive step
execOnce(this, i+1, max);
});
});
OK so I am making a reaction tester, and I have a function that makes shapes appear on screen, So what I want is some sort of function were after 5 clicks on a certain element it will end a function. Is there a way of doing that? sorry if its a dumb question, its because I am new to the whole coding...
Here you go
var clickHandler = (function (e) {
var count = 0;
return function () {
count += 1;
if (count > 5) {
return;
}
// do other stuff here
}
}());
aDiv.addEventListener('click', clickHandler, false);
You Can use static variable to count how many times the object has been clicked.
and here is how you can create static variable in javascript.
You can unbind the click event once the counter reaches 5. See the example below
function test(sender) {
sender.dataset.clicked++;
console.log("I've been clicked", sender.dataset.clicked);
if (+sender.dataset.clicked === 5) {
// unbind the event
sender.onclick = null;
}
return;
}
<div onclick="test(this);" data-clicked="0">click me</div>
You may use global variable which may remain counting on click function
<script>
var globalvar = 0;
onclickfunct()
{
globalvar += 1;
if(globalvar == 5)
{
//do my work
}
else
{
//give alert
}
}
</script>
Right now I'm using javascript to get the job done but i'm using onkeyup. This won't work for me because i'm using a button to populate the "var first". When a user pushes the button it populates the var first so there is no actual keyup/keydown.
<script>
window.onload = function () {
var first = document.getElementById('USERDEFINE1'),
//second = document.getElementById('ADDRESS2');
third = document.getElementById('PHONENIGHT');
fourth = document.getElementById('INTERNET');
fifth = document.getElementById('last_purchase');
sixth = document.getElementById('last_purchase_date');
first.onkeyup = function () { // or first.onchange
//second.value = '4444';
third.value = '111-111-1111';
fourth.value = 'NONE';
fifth.value = 'N/A';
sixth.value = 'N/A';
};
};
</script>
could i use something like:
if (document.getElementById(first.value) > 0
and if so how do i implement into my current javascript or should i rewrite it all together? Thanks in advance.
You will simply have to run the same population logic after pressing the button and after pressing a key in that input. You can also rely on other events like change, depending on how dynamic you want your interface to be.
Basically,
function populateInputs() {
//if we are in here, it means the value of first is > than 0
//at this point you can populate your other inputs
}
document.getElementById('your-button-id').addEventListener('click', function () {
first.value = 10; //init the value greater than 0
populateInputs(); //we know value is greater so populate
});
first.addEventListener('keyup', function () {
if (first.value > 0) {
populateInputs();
} else {
//first.value is not greater than 0
//reset input values to N/A or blank?
}
});
Add multiple items to text-area with duplicate items.
I have one text-area which store data after clicked add data link.
How can i prevent add duplicate items to text-area?
JavaScript call DOM event:
var Dom = {
get: function(el) {
if (typeof el === 'string') {
return document.getElementById(el);
} else {
return el;
}
},
add: function(el, dest) {
var el = this.get(el);
var dest = this.get(dest);
dest.appendChild(el);
},
remove: function(el) {
var el = this.get(el);
el.parentNode.removeChild(el);
}
};
var Event = {
add: function() {
if (window.addEventListener) {
return function(el, type, fn) {
Dom.get(el).addEventListener(type, fn, false);
};
} else if (window.attachEvent) {
return function(el, type, fn) {
var f = function() {
fn.call(Dom.get(el), window.event);
};
Dom.get(el).attachEvent('on' + type, f);
};
}
}()
};
JQuery add data to textarea:
$("#lkaddlanguage").click(function(){
var totalstring;
var checkconstring = $("#contentlng").text();
var strLen = checkconstring.length;
myStr = checkconstring.slice(0,strLen-1);
//alert(myStr);
var checkedItemsArray = myStr.split(";");
var j = 0;
var checkdup=0;
totalstring=escape($("#textval").val()) ;
var i = 0;
var el = document.createElement('b');
el.innerHTML = totalstring +";";
Dom.add(el, 'txtdisplayval');
Event.add(el, 'click', function(e) {
Dom.remove(this);
});
});
HTML Display data
<input type="textbox" id="textval">
<a href="#lnk" id="lkaddlanguage" >Add Data</a>
<textarea readonly id="txtdisplayval" ></textarea>
This seems a very straightforward requirement to me, so I'm not quite clear where you're getting stuck. I have not tried too hard to figure out your existing code given that you are referencing elements not shown in your html ("contentlng"). Also, mixing your own DOM code with jQuery seems a bit pointless. You don't need jQuery at all, but having chosen to include it why then deliberate not use it?
Anyway, the following short function will keep a list of current items (using a JS object) and check each new item against that list. Double-clicking an item will remove it. I've put this in a document ready, but you can manage that as you see fit:
<script>
$(document).ready(function() {
var items = {};
$("#lkaddlanguage").click(function(){
var currentItem = $("#textval").val();
if (currentItem === "") {
alert("Please enter a value.");
} else if (items[currentItem]) {
alert("Value already exists.");
} else {
items[currentItem] = true;
$("#txtdisplayval").append("<span>" + currentItem + "; </span>");
}
// optionally set up for entry of next value:
$("#textval").val("").focus();
return false;
});
$("#txtdisplayval").on("dblclick", "span", function() {
delete items[this.innerHTML.split(";")[0]];
$(this).remove();
});
});
</script>
<input type="textbox" id="textval">
<a href="#lnk" id="lkaddlanguage" >Add Data</a><br>
<div id="txtdisplayval" ></div>
<style>
#txtdisplayval {
margin-top: 5px;
width : 200px;
height : 100px;
overflow-y : auto;
border : 1px solid black;
}
</style>
Note I'm using a div (styled to have a border and allow vertical scrolling) instead of a textarea.
As you can see I've coded it to display an alert for duplicate or empty items, but obviously you could remove that and just ignore duplicates (or substitute your own error handling). Also I thought it might be handy to clear the entry field and set focus back to it ready for entry of the next value, but of course you can remove that too.
Working demo: http://jsfiddle.net/LTsBR/1/
I'm confused.
The only variable that might have duplicates comes from:
var checkedItemsArray = myStr.split(";");
However, checkedItemsArray is not used for anything.
Incidentally, the escape method is deprecated in favour of encodeURIComopnent.
When setting the value of the textarea, do just that: assign to its value property, not to its innerHTML (it can't have markup inside it or any elements, only text nodes).
If you want to check that the members of checkedItemsArray are unique, and you don't mind if they are sorted, you can use a simple function like:
function unique(arr) {
arr.sort();
var i = arr.length;
while (i--) {
if (arr[i] == arr[i - 1]) {
arr.splice(i, 1);
}
}
return arr;
}
Orignal order can be maintained, but it's a bit more code.
I am trying to implemented this jQuery news ticker style plugin from http://www.makemineatriple.com/2007/10/bbcnewsticker
Like mentioned in the comments (around May) there is a bug and the author lost its will to give a bug fix.
The bug is:
In Mac browsers (Firefox, Opera and Safari, all OSX) - links (a href) don’t ‘work’ until each list item has finished scrolling/revealing. Basically after this plugin has loaded, all the a href stops working.
Here is the code for the plugin (http://plugins.jquery.com/project/BBCnewsTicker):
/*
News ticker plugin (BBC news style)
Bryan Gullan,2007-2010
version 2.2
updated 2010-04-04
Documentation at http://www.makemineatriple.com/news-ticker-documentation/
Demo at http://www.makemineatriple.com/jquery/?newsTicker
Use and distrubute freely with this header intact.
*/
(function($) {
var name='newsTicker';
function runTicker(settings) {
tickerData = $(settings.newsList).data('newsTicker');
if(tickerData.currentItem > tickerData.newsItemCounter){
// if we've looped to beyond the last item in the list, start over
tickerData.currentItem = 0;
}
else if (tickerData.currentItem < 0) {
// if we've looped back before the first item, move to the last one
tickerData.currentItem = tickerData.newsItemCounter;
}
if(tickerData.currentPosition == 0) {
if(tickerData.newsLinks[tickerData.currentItem].length > 0) {
$(tickerData.newsList).empty().append('<li></li>');
}
else {
$(tickerData.newsList).empty().append('<li></li>');
}
}
//only start the ticker itself if it's defined as animating: otherwise it's paused or under manual advance
if (tickerData.animating) {
if( tickerData.currentPosition % 2 == 0) {
var placeHolder = tickerData.placeHolder1;
}
else {
var placeHolder = tickerData.placeHolder2;
}
if( tickerData.currentPosition < tickerData.newsItems[tickerData.currentItem].length) {
// we haven't completed ticking out the current item
var tickerText = tickerData.newsItems[tickerData.currentItem].substring(0,tickerData.currentPosition);
if(tickerData.newsLinks[tickerData.currentItem].length > 0) {
$(tickerData.newsList + ' li a').text(tickerText + placeHolder);
}
else {
$(tickerData.newsList + ' li').text(tickerText + placeHolder);
}
tickerData.currentPosition ++;
setTimeout(function(){runTicker(settings); settings = null;},tickerData.tickerRate);
}
else {
// we're on the last letter of the current item
if(tickerData.newsLinks[tickerData.currentItem].length > 0) {
$(tickerData.newsList + ' li a').text(tickerData.newsItems[tickerData.currentItem]);
}
else {
$(tickerData.newsList + ' li').text(tickerData.newsItems[tickerData.currentItem]);
}
setTimeout(function(){
if (tickerData.animating) {
tickerData.currentPosition = 0;
tickerData.currentItem ++;
runTicker(settings); settings = null;
}
},tickerData.loopDelay);
}
}
else {// settings.animating == false
// display the full text of the current item
var tickerText = tickerData.newsItems[tickerData.currentItem];
if(tickerData.newsLinks[tickerData.currentItem].length > 0) {
$(tickerData.newsList + ' li a').text(tickerText);
}
else {
$(tickerData.newsList + ' li').text(tickerText);
}
}
}
// Core plugin setup and config
jQuery.fn[name] = function(options) {
// Add or overwrite options onto defaults
var settings = jQuery.extend({}, jQuery.fn.newsTicker.defaults, options);
var newsItems = new Array();
var newsLinks = new Array();
var newsItemCounter = 0;
// Hide the static list items
$(settings.newsList + ' li').hide();
// Store the items and links in arrays for output
$(settings.newsList + ' li').each(function(){
if($(this).children('a').length) {
newsItems[newsItemCounter] = $(this).children('a').text();
newsLinks[newsItemCounter] = $(this).children('a').attr('href');
}
else {
newsItems[newsItemCounter] = $(this).text();
newsLinks[newsItemCounter] = '';
}
newsItemCounter ++;
});
var tickerElement = $(settings.newsList); // for quick reference below
tickerElement.data(name, {
newsList: settings.newsList,
tickerRate: settings.tickerRate,
startDelay: settings.startDelay,
loopDelay: settings.loopDelay,
placeHolder1: settings.placeHolder1,
placeHolder2: settings.placeHolder2,
controls: settings.controls,
ownControls: settings.ownControls,
stopOnHover: settings.stopOnHover,
newsItems: newsItems,
newsLinks: newsLinks,
newsItemCounter: newsItemCounter - 1, // -1 because we've incremented even after the last item (above)
currentItem: 0,
currentPosition: 0,
firstRun:1
})
.bind({
stop: function(event) {
// show remainder of the current item immediately
tickerData = tickerElement.data(name);
if (tickerData.animating) { // only stop if not already stopped
tickerData.animating = false;
}
},
play: function(event) {
// show 1st item with startdelay
tickerData = tickerElement.data(name);
if (!tickerData.animating) { // if already animating, don't start animating again
tickerData.animating = true;
setTimeout(function(){runTicker(tickerData); tickerData = null;},tickerData.startDelay);
}
},
resume: function(event) {
// start from next item, with no delay
tickerData = tickerElement.data(name);
if (!tickerData.animating) { // if already animating, don't start animating again
tickerData.animating = true;
// set the character position as 0 to ensure on resume we start at the right point
tickerData.currentPosition = 0;
tickerData.currentItem ++;
runTicker(tickerData); // no delay when resuming.
}
},
next: function(event) {
// show whole of next item
tickerData = tickerElement.data(name);
// stop (which sets as non-animating), and call runticker
$(tickerData.newsList).trigger("stop");
// set the character position as 0 to ensure on resume we start at the right point
tickerData.currentPosition = 0;
tickerData.currentItem ++;
runTicker(tickerData);
},
previous: function(event) {
// show whole of previous item
tickerData = tickerElement.data(name);
// stop (which sets as non-animating), and call runticker
$(tickerData.newsList).trigger("stop");
// set the character position as 0 to ensure on resume we start at the right point
tickerData.currentPosition = 0;
tickerData.currentItem --;
runTicker(tickerData);
}
});
if (settings.stopOnHover) {
tickerElement.bind({
mouseover: function(event) {
tickerData = tickerElement.data(name);
if (tickerData.animating) { // stop if not already stopped
$(tickerData.newsList).trigger("stop");
if (tickerData.controls) { // ensure that the ticker can be resumed if controls are enabled
$('.stop').hide();
$('.resume').show();
}
}
}
});
}
tickerData = tickerElement.data(name);
// set up control buttons if the option is on
if (tickerData.controls || tickerData.ownControls) {
if (!tickerData.ownControls) {
$('<ul class="ticker-controls"><li class="play">Play</li><li class="resume">Resume</li><li class="stop">Stop</li><li class="previous">Previous</li><li class="next">Next</li></ul>').insertAfter($(tickerData.newsList));
}
$('.play').hide();
$('.resume').hide();
$('.play').click(function(event){
$(tickerData.newsList).trigger("play");
$('.play').hide();
$('.resume').hide();
$('.stop').show();
event.preventDefault();
});
$('.resume').click(function(event){
$(tickerData.newsList).trigger("resume");
$('.play').hide();
$('.resume').hide();
$('.stop').show();
event.preventDefault();
});
$('.stop').click(function(event){
$(tickerData.newsList).trigger("stop");
$('.stop').hide();
$('.resume').show();
event.preventDefault();
});
$('.previous').click(function(event){
$(tickerData.newsList).trigger("previous");
$('.stop').hide();
$('.resume').show();
event.preventDefault();
});
$('.next').click(function(event){
$(tickerData.newsList).trigger("next");
$('.stop').hide();
$('.resume').show();
event.preventDefault();
});
};
// tell it to play
$(tickerData.newsList).trigger("play");
};
// News ticker defaults
jQuery.fn[name].defaults = {
newsList: "#news",
tickerRate: 80,
startDelay: 100,
loopDelay: 3000,
placeHolder1: " |",
placeHolder2: "_",
controls: true,
ownControls: false,
stopOnHover: true
}
})(jQuery);
Any solutions? I am not a programmer so if someone could point out where to patch it greatly appreciated!
UPDATE: it seems only the links with ? mark becomes disabled.
Example: http://url.com/blog/index.html?page=2
I just happened to come across this post. I do still support the ticker, and there have been a few releases since last July.
The way to mitigate this issue was that there's now a "stop on hover" option, which pauses the ticker and completes (immediately) the display of an item when the user hovers over it (including of course being about to click it).
If this is still of relevance to you, if you still have issues with the latest version it'd be worth reading through the thread of comments; please do get in touch if you've still a problem (if one of the comments was yours and I missed it, then sorry!). The "official" way is to post a bug report on the jQuery plugins site, which fully tracks any reported issues, but I do try to respond to anyone who requests support via the blog.
If there are any elements with the ID of news in your document, there might be a collision happening... Might this be the case? I'd search your html document for any occurrences of id="news" and correct them, seeing as though passing the proper parameters into the plugin might require a bit of extra research.