Crazy Horse
asked on
Getting search results to display ul for text field being inputted into
I had this question after viewing Ajax php search with multiple text fields generated by php.
So, I tried to think about what everyone has said and based on that I have tried to come up with a solution that doesn't require using ID's. At this stage I don't even want to add complexity by adding data-id values.
To recap, an unknown number of text fields will be generated by a user. Depending on their input, 1 could be echoed out, 3 could be echoed out etc. All of which will be identical and look pretty much like this: (I have put 2 together for this example)
When results are displayed and a user clicks on a li, that text displays in the text field closest or associated to that text field which is exactly what I want.
The problem I am having though is getting the ul to only display in the text field the person is typing in. What is happening now is that if I type in text field 1, the ul shows for both text fields which is wrong. I am struggling to get this line right.
Here is the full jQuery: ( I commented out the line that did work to display records, but for both text fields instead of just the one)
So, I tried to think about what everyone has said and based on that I have tried to come up with a solution that doesn't require using ID's. At this stage I don't even want to add complexity by adding data-id values.
To recap, an unknown number of text fields will be generated by a user. Depending on their input, 1 could be echoed out, 3 could be echoed out etc. All of which will be identical and look pretty much like this: (I have put 2 together for this example)
<div class="form-group">
<input type="text" name="name" autocomplete="off">
<div class="result"></div>
</div>
<div class="form-group">
<input type="text" name="name" autocomplete="off">
<div class="result"></div>
</div>
When results are displayed and a user clicks on a li, that text displays in the text field closest or associated to that text field which is exactly what I want.
The problem I am having though is getting the ul to only display in the text field the person is typing in. What is happening now is that if I type in text field 1, the ul shows for both text fields which is wrong. I am struggling to get this line right.
$(".result").closest("ul").html(data);
Here is the full jQuery: ( I commented out the line that did work to display records, but for both text fields instead of just the one)
$(document).ready(function() {
$("input").keyup(function() {
var input = $(this).val();
$(".loader").show();
if (input.length > 3) {
$.ajax({
type: 'POST',
url: 'insert-ajax.php',
data: {
name: input
},
success: function(data) {
if (!data.error) {
// $(".result").html(data);
$(".result").closest("ul").html(data);
$(".loader").hide();
}
}
});
}
if (input.length < 1) {
$(".loader").hide();
$(".result").html("");
}
});
$(".result").on("click", "li", function() {
console.log($(this).text());
$(this).closest(".result").siblings("input").val($(this).text());
});
});
>> $(this).closest(".result") .siblings( "input").v al($(this) .text());
Based on the markup you supplied, try the following instead:
Based on the markup you supplied, try the following instead:
$('input',$(this).closest(".form-group")).val($(this).text());
ASKER
@ hielo,
I tried that but no results appeared when typing. That part of the code was working as expected though, which was that when a li was clicked, that particular choice then displayed in the text field.
The code that is the issue I believe is
This isn't doing anything. Before that, I had:
But that problem with that is the unordered list showed under both text fields instead of the one I am typing in. I have uploaded a screenshot where you can see I typed "test" in the top field but results are showing under both fields.
I tried that but no results appeared when typing. That part of the code was working as expected though, which was that when a li was clicked, that particular choice then displayed in the text field.
The code that is the issue I believe is
$("ul").closest(".result").html(data);
This isn't doing anything. Before that, I had:
$(".result").html(data);
But that problem with that is the unordered list showed under both text fields instead of the one I am typing in. I have uploaded a screenshot where you can see I typed "test" in the top field but results are showing under both fields.
ASKER
@ Ray,
I have seen the name "AngluarJS" around a lot lately but have been too intimidated to look at what it actually is, but your advice is usually always good so I think that since I haven't delved too much into jQuery, perhaps I should rather focus on learning AngularJS instead of jQuery? Or is this another situation where I should try to learn both?
Man, there is a lot to learn out there. This seems like it's going to take a lifetime :P
I have seen the name "AngluarJS" around a lot lately but have been too intimidated to look at what it actually is, but your advice is usually always good so I think that since I haven't delved too much into jQuery, perhaps I should rather focus on learning AngularJS instead of jQuery? Or is this another situation where I should try to learn both?
Man, there is a lot to learn out there. This seems like it's going to take a lifetime :P
Are you are referring to line 16 of your original code? If so, try:
$(".result", $(this).closest("form-group")) .html(data);
ASKER
@ hielo,
Yes, that's the exact line. I tried your new code and still no search results/list items appear.
Yes, that's the exact line. I tried your new code and still no search results/list items appear.
>> I tried your new code and still no search results
I missed a period on my last post. It should be ".form-group" (notice the period to the left of "form-group").
I missed a period on my last post. It should be ".form-group" (notice the period to the left of "form-group").
ASKER
Ah, okay. I tried that and still nothing.
$(".result", $(this).closest(".form-group")) .html(data);
Do you have a link to your page?
ASKER
Hi,
Unfortunately not, it's running on localhost. Will it help you if I can get it on a live server?
Unfortunately not, it's running on localhost. Will it help you if I can get it on a live server?
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
AngularJS is a framework
jQuery is a library
Important to understand the difference between the two. jQuery is fine for this setup.
Next lets look at some issues with your code
Now lets look at a solution that works
Working sample here
Once you get that working we can look at adding the loader
Also note - your <input>'s have the same name attributes - you might need to change these of you need to use them on the server - in my code I have used arrays to get around the dynamic creation issue
jQuery is a library
Important to understand the difference between the two. jQuery is fine for this setup.
Next lets look at some issues with your code
//$(document).ready(function() {
// I prefer $(function
$(function() {
$("input").keyup(function() {
var input = $(this).val();
$(".loader").show();
if (input.length > 3) {
$.ajax({
type: 'POST',
url: 'insert-ajax.php',
data: {
name: input
},
success: function(data) {
// PROBLEM: THE if SAYS YOU ARE EXPECTING AN OBJECT BACK - SOMETHING LIKE
// {
// error: 'Something here'
// }
// BUT ...
if (!data.error) {
// $(".result").html(data);
// OVER HERE YOU ARE USING IT AS AN HTML RETURN
// ALSO .result IS GOING TO RETURN THE FIRST .result IT FINDS
// ON THE PAGE - NOT RELATIVE TO THE INPUT
$(".result").closest("ul").html(data);
$(".loader").hide();
}
}
});
}
if (input.length < 1) {
$(".loader").hide();
$(".result").html("");
}
});
$(".result").on("click", "li", function() {
console.log($(this).text());
$(this).closest(".result").siblings("input").val($(this).text());
});
});
Now lets look at a solution that works
<script>
$(function() {
$("input").keyup(function() {
// GET A REFERENCE TO THE <input> THAT TRIGGERED THE EVENT
var jqo = $(this);
// GET THE VALUE
var input = jqo.val();
// FIND THE result <div> FOR THIS <input>
var result = jqo.closest('div').find('.result');
if (input.length > 3) {
// GET DATA FROM SERVER
$.ajax({
type: 'POST',
url: 't2185.php',
data: {
name: input
}
})
// .success IS ON ITS WAY OUT
// I PREFER promise INTERFACE
.done(function(data) {
// data IS PLAIN HTML SO ADD IT TO OUR
// result WINDOW
result.html(data);
})
}
// THE REST IS THE SAME
if (input.length < 1) {
result.html("");
}
});
$(".result").on("click", "li", function() {
console.log($(this).text());
$(this).closest(".result").siblings("input").val($(this).text());
});
});
</script>
Working sample here
Once you get that working we can look at adding the loader
Also note - your <input>'s have the same name attributes - you might need to change these of you need to use them on the server - in my code I have used arrays to get around the dynamic creation issue
ASKER
Thanks Julian! I got that working now. I also added (hopefully the correct code) a line to remove the UL list once an option has been selected otherwise it still hangs around after making a selection.
My idea was that they all have the same name (text inputs) as I am using the class and not ID for the jQuery and then when I insert the records I will just use a foreach loop. So, "name" would have to be "name[]".
I did try play around with some code now for the loader. I can get it to show up but not in the right place. Ideally I would like it to show in the right hand side of the text field.
$(this).closest(".result").hide()
My idea was that they all have the same name (text inputs) as I am using the class and not ID for the jQuery and then when I insert the records I will just use a foreach loop. So, "name" would have to be "name[]".
I did try play around with some code now for the loader. I can get it to show up but not in the right place. Ideally I would like it to show in the right hand side of the text field.
So, "name" would have to be "name[]"Firstly, name <> name[] - the former will only come through in PHP $_POST as the last value submitted.
Consider this example
<form action="reflect.php" method="post">
<input name="name" value="fred" />
<input name="name" value="frank" />
<input name="name" value="frida" />
<input type="submit" />
</form>
Result$_POST
Array
(
[name] => frida
)
However if we do the follwoingecho file_get_contents('php://input');
We get thisname=fred&name=frank&name=frida
PHP takes the last value for the $_POST['name']name[] on the other hand
<form action="reflect.php" method="post">
<input name="name[]" value="fred" />
<input name="name[]" value="frank" />
<input name="name[]" value="frida" />
<input type="submit" />
</form>
Gives us$_POST
Array
(
[name] => Array
(
[0] => fred
[1] => frank
[2] => frida
)
)
Secondly, if you look at the sample I posted you will see I used name[]
ASKER
Hi Julian,
Sorry, maybe I didn't explain what I was trying to say properly but I was agreeing with you. I needed to change my input name from just name to name[] so I could use foreach when doing the insert otherwise it would only insert one record instead of all of them.
Sorry, maybe I didn't explain what I was trying to say properly but I was agreeing with you. I needed to change my input name from just name to name[] so I could use foreach when doing the insert otherwise it would only insert one record instead of all of them.
$(this).closest(".result").hide()
I would rather do this<script>
$(function() {
$("input").keyup(function() {
// GET A REFERENCE TO THE <input> THAT TRIGGERED THE EVENT
var jqo = $(this);
// GET THE VALUE
var input = jqo.val();
// FIND THE result <div> FOR THIS <input>
var result = jqo.closest('div').find('.result');
if (input.length > 3) {
// GET DATA FROM SERVER
$.ajax({
type: 'POST',
url: 't2185.php',
data: {
name: input
}
})
// .success IS ON ITS WAY OUT
// I PREFER promise INTERFACE
.done(function(data) {
// data IS PLAIN HTML SO ADD IT TO OUR
// result WINDOW
result.html(data);
result.data('input', jqo);
})
}
// THE REST IS THE SAME
if (input.length < 1) {
result.html("");
}
});
$('.result').on('click','li', function() {
var res = $(this).closest('.result');
res.data('input').val(this.textContent);
res.html('');
});
});
</script>
Breaking it downFirst up this line
result.data('input', jqo);
This is taking advantage of jQuery objects. I assign the input control for the .result container to a data attribute on the element. The jQuery data() method allows you to assign a scalar value (string, number) or an object. The jqo is the jQuery object we created at the beginning so we could access the input val. Now in our event handler
$('.result').on('click','li', function() {
var res = $(this).closest('.result');
res.data('input').val(this.textContent);
res.html('');
});
The input is available on the result container - so we save ourselves an extra find. We can even take this one step further
$('.result').on('click', function(evt) {
$(this).data('input').val(evt.target.textContent);
this.textContent = '';
});
Here we bind to the .result container - not the li. We know the children will be li's so we can use this code.We use the event.target to get the content we want to send to the input - but because the event is firing on the .result container we don't have to use a .closest() to find the container to get the input data attribute and clear the contents.
I have updated the sample online with the above (using the last method)
Sorry, maybe I didn't explain what I was trying to say properly but I was agreeing with you.No, I did understand - I just provided that post for background because there is often a misconception that when using a non array duplicate data is lost. It is only lost when accessing the $_POST array but you can still get the data using php://input - and then parse it yourself.
ASKER
Ah, I see!
In case you missed it check the post before my last one for the solution on how to clear the <ul>
ASKER
I did see that, thanks so much! :)
Should I post a related question if I get stuck with trying to show the loader?
Should I post a related question if I get stuck with trying to show the loader?
Up to you - the loader depends on whether you want one per input or a global loader. The latter is dead easy you just hide and show it. The former is also easy but have to reference it relative to its container.
ASKER
If you wouldn't mind, let's try add the loader in this question and I will ask a related question for the next part ;)
Ideally, I would like the loader to display in the text field I am typing in (right hand side of text field)
Not sure if I will be able to get it right, but I will give it a go.
Ideally, I would like the loader to display in the text field I am typing in (right hand side of text field)
Not sure if I will be able to get it right, but I will give it a go.
What does the loader look like - can you post it.
ASKER
Hi Julian,
Will do so this evening as soon as I am back in front of my development machine.
Will do so this evening as soon as I am back in front of my development machine.
ASKER
What does the loader look like - can you post it.
I have uploaded it for you to this post.
ajax-loader.gif
ASKER
I tried it out myself and I got the loader to display for the correct text fields but it is showing under the text fields instead of in them. But I assume this is because I have the loader div underneath the text field div. I am going to try see if I can use CSS to position it but not sure if that would be the right way of positioning it.
I added this code:
and then changed the HTML to:
I added this code:
// FIND THE CLOSEST LOADER FOR THIS INPUT
var loader = jqo.closest('div').find('.loader');
$(loader).show();
and then changed the HTML to:
<div class="form-group">
<input type="text" name="name" autocomplete="off">
<div style="display:none" class="loader">
<img src="../build/css/ajax-loader.gif" />
</div>
<div class="result"></div>
</div>
ASKER
Hmm. CSS doesn't work because the loader is always in exactly the same place.
.loader {
position: absolute;
top: 0%;
left: 18.5%;
}
did you make .form-group position: relative?
ASKER
I didn't. But I just tried that now (I think). Like this:
This makes the positioning weird and not in the right place.
.loader {
position: absolute;
top: 0%;
left: 18.5%;
}
.form-group {
position: relative;
}
This makes the positioning weird and not in the right place.
Explain to me again exactly how you want the loader to look
Do you have a screen shot?
Are you using an image / spinner (.gif) as part of the loader?
I can help you but I need to know what I am aiming at.
Do you have a screen shot?
Are you using an image / spinner (.gif) as part of the loader?
I can help you but I need to know what I am aiming at.
ASKER
For sure. Sorry for not being more specific. I have done this in photoshop quickly.
The loader should show in this location when you type in that specific input. I have just pasted it into both so you can see where it should go in each one.
Yes, it is an animated gif and it spins round and round.
example-loader.jpg
The loader should show in this location when you type in that specific input. I have just pasted it into both so you can see where it should go in each one.
Yes, it is an animated gif and it spins round and round.
example-loader.jpg
ASKER
This is what it currently does, lol.
wrong.jpg
wrong.jpg
ASKER
If you check this comment and click on the gif, you will see it spinning.
Got it ... working on it.
ASKER
Awesome, thanks!
Here is a first attempt
CSS
Updated sample here
The jQuery changes are arbitrary - the main changes are the styling to the HTML to position the loader. You can play around with the styling. There are different options depending on the look you want to achieve.
I have added a sleep to the PHP script to simulate a server delay so you can see the spinner.
CSS
<style type="text/css">
.form-group {
position: relative;
width: 25%;
}
input {
width: 100%;
height: 30px;
}
.loader {
display: none;
position: absolute;
right: 0;
top: 0;
height: 100%;
background: rgba(0,0,0,0.15) url(images/loading2.gif) no-repeat right center;
width: 100%;
}
</style>
HTML<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<div class="loader"></div>
<div class="result"></div>
</div>
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<ul class="result"></ul>
</div>
jQuery<script>
$(function() {
$("input").keyup(function() {
// GET A REFERENCE TO THE <input> THAT TRIGGERED THE EVENT
var jqo = $(this);
// GET THE VALUE
var input = jqo.val();
// FIND THE result <div> FOR THIS <input>
var parent = jqo.closest('div');
var result = parent.find('.result');
if (input.length > 3) {
var loader = parent.find('.loader').show();
// GET DATA FROM SERVER
$.ajax({
type: 'POST',
url: 't2185.php',
data: {
name: input
}
})
// .success IS ON ITS WAY OUT
// I PREFER promise INTERFACE
.done(function(data) {
// data IS PLAIN HTML SO ADD IT TO OUR
// result WINDOW
result.html(data);
result.data('input', jqo);
loader.hide();
})
}
// THE REST IS THE SAME
if (input.length < 1) {
result.html("");
}
});
$('.result').on('click', function(evt) {
$(this).data('input').val(evt.target.textContent);
this.textContent = '';
});
});
</script>
Updated sample here
The jQuery changes are arbitrary - the main changes are the styling to the HTML to position the loader. You can play around with the styling. There are different options depending on the look you want to achieve.
I have added a sleep to the PHP script to simulate a server delay so you can see the spinner.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
This is pretty darn awesome. There is only one problem I can see at this point though. The inputs are going to be generated by php meaning that they are going to be identical. The HTML you changed shows that the first input field is different to the second, third, fourth or however many are generated. They all need to be exactly the same as in my particular use case.
For example, if the simple equation calculated from a user input is 3, then 3 inputs will be echoed. If the result is 4, 4 text fields will be echoed etc.
Only the first input here has the loader div. Where if PHP was echoing it out, both would have it like this:
For example, if the simple equation calculated from a user input is 3, then 3 inputs will be echoed. If the result is 4, 4 text fields will be echoed etc.
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<div class="loader"></div>
<div class="result"></div>
</div>
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<ul class="result"></ul>
</div>
Only the first input here has the loader div. Where if PHP was echoing it out, both would have it like this:
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<div class="loader"></div>
<div class="result"></div>
</div>
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<div class="loader"></div>
<div class="result"></div>
</div>
That was a mistake - the HTML should be
If you look at the sample link I posted above and go to the HTML tab of the source window you will see the HTML for the blocks is identical
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<ul class="result"></ul>
</div>
<div class="form-group">
<input type="text" name="name[]" autocomplete="off">
<ul class="result"></ul>
</div>
Note the latest sample does not have a .loader DIV because we are styling the <input> directly.If you look at the sample link I posted above and go to the HTML tab of the source window you will see the HTML for the blocks is identical
ASKER
Ah.
So, I changed it a little because I didn't want the text field to be disabled. It is working pretty much exactly how I had imagined it in my mind which is great, thanks so much! I just wanted to ask you about something though if you don't mind. Firstly, what do you mean by this:
And secondly,
why does this remove the loader and the text field
but this just removes the loader:
I thought the disabled: false bit just meant that you were able to type in the text field again as disabled:true is what disabled the text field.
So, I changed it a little because I didn't want the text field to be disabled. It is working pretty much exactly how I had imagined it in my mind which is great, thanks so much! I just wanted to ask you about something though if you don't mind. Firstly, what do you mean by this:
.success IS ON ITS WAY OUT
And secondly,
why does this remove the loader and the text field
jqo.removeClass('loader').hide();
but this just removes the loader:
jqo.removeClass('loader').prop({disabled: false});
I thought the disabled: false bit just meant that you were able to type in the text field again as disabled:true is what disabled the text field.
jqo refers to the <input> so when you say
when you say
Remember the new solution uses CSS to style the <input> with the loader overlay - there is no element that represents the loader so there is nothing to hide - all we are doing is adding / removing a class that adds / removes styling to the <input>
The disabled bit I added on to prevent users from typing in the box while a lookup was being done.
.success() is being deprecated in favour of the promise return
Refer to this link http://api.jquery.com/jQuery.ajax/#jqXHR
To summarise:
The solution above uses CSS to style the <input> to give the appearance of having a loader overlaid on top of it when in reality all we are doing is adding a class that has a background-image positioned right.
jqo.removeClass('loader')
- it removes the class that adds the spinner to the <input>when you say
jqo.removeClass('loader').hide()
you are saying hide the <input>.Remember the new solution uses CSS to style the <input> with the loader overlay - there is no element that represents the loader so there is nothing to hide - all we are doing is adding / removing a class that adds / removes styling to the <input>
The disabled bit I added on to prevent users from typing in the box while a lookup was being done.
.success() is being deprecated in favour of the promise return
Refer to this link http://api.jquery.com/jQuery.ajax/#jqXHR
Deprecation Notice: The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks are removed as of jQuery 3.0. You can use jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead.
To summarise:
The solution above uses CSS to style the <input> to give the appearance of having a loader overlaid on top of it when in reality all we are doing is adding a class that has a background-image positioned right.
ASKER
Thanks for the explanation, it helps a lot. Well, this is working perfectly now. I just want to style the unordered list and then it's on to the next part! Thanks a million, this was really giving me a headache.
You are welcome.
What you're asking about here has always been thorny in jQuery (a 12-year old library) and has been perfectly solved by AngularJS (a 5-year old library). AngularJS gives you two way data bindings between the view and the model. A change in either is reflected in the other. So you don't have to deal with the problem of which selector goes with which input - you just bind a new input to the generated input control and bind a new output to the generated response area. Instead of starting with HTML (which was never designed for dynamic views) and using JavaScript (jQuery) to manipulate named elements, with AngularJS you build the DOM in real time, in the client browser. Typically server communication is with AJAX requests and JSON. It's not easy to learn AngularJS, but it's very cool when you see it in action.
https://angularjs.org/