QAutocomplete in 2.0
Hi,
Is anyone currently working on QAutocomplete in 2.0 HEAD? I couldn't get it to work, but I don't know exactly how it is supposed to be used.
I was able to (easily) get the QAjaxAutoCompleteTextBox to work from the contributed plugins. It appears that QAjaxAutoCompleteTextBox has a different API than the QAutocomplete in HEAD.
I understand that QAutocomplete had much of its code autogenerated from Jquery, so maybe it's not supposed to work yet.
Does anyone know what the goal is? Is the goal to duplicate the api and usage that currently exists in the QAjaxAutoCompleteTextBox plugin?
For example, the plugin can take a function name as the second parameter to the create function and that gets called to build the list.
Conversely, QAutocomplete does not take a second parameter on create, but does has new events defined which look like they are designed to accomodate the same feature, but I was not able to get it to work.
Any hints on the direction of this component woud be appreciated.
Thanks,

As a slightly separate topic, is there a way, using the QAjaxAutoCompleteTextBox plugin to send back an array of the information to display as well as other information associated with each row?
For example, I want to return a list of names that match what the user is typing, but when they select one of those names, I need to know that names id attribute.
Cliff, using the QAjaxAutoCompleteTextBox, you can add data, to be returned to the callback, by separating it from the data to be displayed in the list by a pipe (|). Here's an example:
public static function LoadCustNameAutoArray($strParameter){$objCustomerArray = Customers::QueryArray(
QQ::OrCondition(
QQ::Like(QQN::Customers()->CustName,'%' . $strParameter . '%')
),
QQ::Clause(QQ::OrderBy(QQN::Customers()->CustName))
);
$custresult = array();
foreach($objCustomerArray as $objCustomer) {
$custresult[] = $objCustomer->CustName . '-' . $objCustomer->BillAddr1 . " - " . $objCustomer->BillCity . "|" . $objCustomer->CustNo;
}
return $custresult;
}
So, you code your text box like this:
$this->txtGetCust = new QAjaxAutoCompleteTextBox($this, 'LoadCustomerArray');$this->txtGetCust->SetSelectCallback($this, 'txtCustomer_Select');
The callback is the method that gets called, when the user makes a selection from the list. You can have whatever string you want following the pipe, if you want an array, you'll need to unserialize it.
Hope this helps.
yeah, I've seen some other forum posts on the topic now. The code I got from the plugin repository though didn't have the SetSelectCallback function defined though. Was that in a patch you applied, or a custom version of the plugin?
Cliff,
QAutocomplete is supposed to be working in HEAD (unless you're using IE and are affected by this bug http://trac.qcu.be/projects/qcubed/ticket/641, which will be fixed very soon). If it's failing in firefox, could you please tell me what the problem is? There is a (admittedly simple) example that comes with these controls under examples/other_controls/jq_example.php
Also this thread has some discussion on the same subject: http://qcu.be/content/new-autocomplete-qcubed-201
As for the goal, it's definitely not to duplicate the API of the old plugin. The goal is to provide wrappers for all the official JQuery UI components as part of QCubed. All these wrappers do share a common API.
QAutocomplete should however be able to provide all the same functionality as the old plugin did. So if something is missing, please let me know.
Thanks.
-Vartan
Cliff, Vartan is indeed correct. The new QAutocomplete should work, at least in 2.0.1. I'm not sure about 2.0, because I haven't tested it. I'm using the Autocomplete as modified by Agsel. Here's the code for QAjaxAutocompleteTextBox, with the callback:
<?php
/**
* Server-side autocomplete text box. Whenever the user types stuff
* in this text box, an asynchronous Ajax request to the server will
* be sent, looking for potential matches.
*
* @package Controls
*/
class QAjaxAutoCompleteTextBox extends QAutoCompleteTextBoxBase {
public $Value=null;
private $strCallback;
/**
* QControl/QForm, which has the method to call in case of "onselect" event.
* Use QAjaxAutoCompleteTextBox::SetSelectCallback($objObject, $strMethod) to set.
* @var QControl|QForm
*/
protected $objSelectCallbackObject;
/**
* Method, which will be called in case of "onselect" event
* Use QAjaxAutoCompleteTextBox::SetSelectCallback($objObject, $strMethod) to set.
* @var string
*/
protected $strSelectCallbackMethod;
/**
* Control proxy, which handles events
* @var QControlProxy
*/
public $pxy;
public function __construct($objParentObject, $strCallback, $strControlId = null) {
parent::__construct($objParentObject, $strControlId);
$this->strCallback = $strCallback;
$this->pxy = new QControlProxy($this);
$this->pxy->AddAction(new QAutoCompleteTextBoxEvent(), new QAjaxControlAction($this, 'callbackAjax'));
$this->SetSelectCallback($this, 'QAuto_Select');
}
/**
* Sets callback to method, which will be executed in case of "onselect" event (when suggested item is selected)
* @param QControl|QForm $objControl
* @param string $strMethod
*/
public function SetSelectCallback($objControl, $strMethod) {
$this->objSelectCallbackObject = $objControl;
$this->strSelectCallbackMethod = $strMethod;
$this->pxy->AddAction(new QSelectEvent(), new QAjaxControlAction($this, 'txt_Select'));
}
public function txt_Select($strFormId, $strControlId, $strParameter) {
$objControl = $this->objSelectCallbackObject;
$strMethod = $this->strSelectCallbackMethod;
if ($objControl) {
$objControl->$strMethod($strParameter);
}
}
public function callbackAjax($strFormId, $strControlId, $strParameter) {
$parent = (isset($this->objParentControl) ? $this->objParentControl : $this->objForm);
try {
$arrToSerialize = $parent->{$this->strCallback}($strParameter);
} catch (Exception $e) {
echo "There's been an error processing auto-complete matches: \n";
throw $e;
}
if (is_null($arrToSerialize)) {
return;
}
if (!is_array($arrToSerialize)) {
echo 'Error: the callback method for QAutoCompleteTextBox must return an array of strings';
throw new QCallerException("callback method for QAutoCompleteTextBox must return an array of strings");
}
foreach ($arrToSerialize as $item) {
echo $item . "\n";
}
// Critically important - stop the processing and don't allow any other
// QCubed events to fire in this case
exit;
}
public function GetScript() {
if(!$this->blnVisible || !$this->blnEnabled) {
return '';
}
// TODO: escape jquery => $
return sprintf('
var objParams = {Qform__FormId:"%s",Qform__FormControl:"%s"};
$j("#%s").autocomplete("%s",
{extraParams:objParams,
%s%s%s%s%s%s%s})%s',
$this->objForm->FormId,
$this->pxy->ControlId,
$this->strControlId,
QApplication::$RequestUri,
"minChars:" . $this->intMinChars,
(($this->blnAutoFill) ? ",autoFill:true" : ""),
(($this->blnMatchContains) ? ",matchContains:true" : ""),
(($this->blnMatchCase) ? ",matchCase:true" : ""),
(($this->blnMustMatch) ? ",mustMatch:true" : ""),
(($this->Width) ? ",width:" . $this->Width : ""),
(($this->strTextMode == QTextMode::MultiLine) ? ",multiple:false" : ""),
// functionality for "onselect"
($this->objSelectCallbackObject ? sprintf(".result(function(event, data, formatted) {
var strParam = '';
if (data[1]) {
strParam = data[0] + '|' + data[1];
} else {
strParam = data;
}
qc.pA('%s', '%s', '%s', strParam, '%s');
})",
$this->objForm->FormId, $this->pxy->ControlId, 'QSelectEvent',
$this->objForm->DefaultWaitIcon ? $this->objForm->DefaultWaitIcon->ControlId : '')
: ""
)
);
}
private function QAuto_Select($mixSelected) {
$strParamArray = explode('|', $mixSelected);
if (count($strParamArray) == 2) {
$this->Value = (int)$strParamArray[1];
//QApplication::DisplayAlert("Value=" . $this->Value);
}
}
Public function SelectedValue(){
if ( strlen($this->Text) > 0)
return $this->Value;
//cleared box after selection
else
return Null;
}
}
?>
I use this extensively and it works well. I'm just waiting for 2.0.2 to upgrade to the new QAutocomplete.
Thanks,
I was able to get it working in head. Now the problem, which didn't appear to have a solution in the thread you linked, is that I also need 'hidden' values.
So, the list has clients and the display of those clients is 'Lastname, FirstName', but I would like the 'value' to be the client ID so that when my 'go' button is clicked, I can redirect to the correct page.
I could just make it "LastName, FirstName (id)" and then explode the list and parse it when needed, but that sounds kinda hacky, and anyway, my client is super picky about display items like that. No matter how good it works, the VERY first thing he will say is 'Can we get rid of those numbers after the name?" I know him well, he won't see anything else until that detail is taken care of.
I could also take the presented name and then on button press do another hit on my DB to get the id, but I'd like to avoid that, because it seems equally hacky, and because it seems like there are a lot of fringe cases where that would fail anyway.
Any Ideas?
Clif,
I understand what you're trying to do, but I think this is not the correct use of an auto-complete text box. The functionality you're looking for is really the normal html select box (drop-down). Here is why: The whole point of the text box, is to allow the user to type values that may not be in your list. In your example, if you're not gonna allow your user to enter non-existing client names, then why not give a simple drop-down?
If you will allow arbitrary names in the text box, then what does your back-end do, if the client name was not in your database? Presumably, you will query the database, see if the client name is absent, and based on that implement your logic (e.g. add it to the database, or return an error, or whatever). But if you have to do that check anyway, then why do you need the browser to send you the ID in the first place? You can easily get the ID with your query.
At the end of the day, an input textbox (whether it's auto-complete or not) is a standard html control, and it's supposed to post back the value that you actually see in the box. So displaying one thing in the text box, but posting something else, is not appropriate for it. That functionality is provided by the "select" dropdown. The autocomplete is really there as a hint or a quick shortcut for entering the text.
I know the old QAutCompleteTextBox plugin provided this feature (using a "|" delimiter), and of course, in principle QAutocomplete can have it too, but I really do think that it's not really useful. A mis-feature, if you will.
BTW, QAutocomplete does provide functionality to display a different label in the autocomplete list, but to set a different value in the textbox itself. But it will still post whatever is shown in the box. Of course this does not accomplish what you described above (since you obviously will not want to show the ID in the textbox).
BTW, if I did not convince you at all, and if you really need the functionality, I think you can still do it with QAutocomplete, by using the the Select event. For example you can set the labels of the autocomplete list to contain some hidden controls that have the client id, then use the select event with a javascript action to get the Id from that hidden control and set it into a hidden input in the form.
Untested (pseudo-)code:
<?php$this->Autocomplete = new QAutocomplete($this);
$source = array();
$source[] = new QAutocompleteListItem('<div>Robin Hood<span style="display:none;">56</span></div>', 'Robin Hood');
$source[] = new QAutocompleteListItem('<div>Little John<span style="display:none;">57</span></div>', 'Little John');
$this->Autocomplete->Source = $source;
$this->Autocomplete->OnSelect = new QJsClosure("$j('#hiddeninput').val($j(ui.item).find('span').text()); return true;", array('event', 'ui'));
?>
I'm not using a select box because I would have 1 thousand entries in my select box and asking people to scroll through 1000 entries in a select box is too much. That's the main reason autocomplete boxes were invented. You have too many entries to browse through in a list, but you can't or would prefer not to allow the user to just type anything, because they have to select a 'valid' record in the end, and you don't want something clunky like a dialog box popping up to confirm or going to a new page to select based on 'close' matches .
Even just in straight jQuery you see this question posted all over forums and boards requesting this functionality. It seems autocomplete used to be a jquery plugin and it had this functionality via the '|' feature, but now that autocomplete is in core, it no longer has the feature, so the internet is littered with people asking how to get that functionality back.
This is not a misuse, but a super common user interface element. Several big packages ship with built-in components that use this interface style. CiviCRM, for example has their 'Quick Search' box to make it easy to get to a contacts record, which is essential when you have 1000's of clients. Drupal has several instances of this as well, like their autocomplete textbox in the masquerade module, which makes it easy to find existing users to impersonate.
In civicrm, you can still type in something the autocomplete box doesn't find, but in that case you _do_ present search results. Where the autocomplete simply searched for matches in firstname/lastname/email, the full search also checks things like addresses and notes etc. The autocomplete simply makes navigation faster, because I don't have to get a search results page if I know I'm looking for joe-whatshisname. I just type 'jo' and it gives me the 10 likely matches and I can quickly get to the record I want.
Another thing, and maybe I don't get the way it is implemented, but since QAutocomplete can take as a source an array of QAutoCompleteListItems (which, btw, suggests it is more akin to a selectbox than a textfield), then, shouldn't it work like a select box, where there is an array of items, and those items are essentially a name and value pair (or an object, actually)? The use of subclasses of QListItem give an expectation that there should be other ListBox-esque things you can do with it.
In other words (and I haven't before seen any example that uses QAutocompleteListItem before) why wouldn't you be able to get 'SelectedItem' for the box and then that selected item would have the equivalent of the Text and Value parameters. This would make it much more 'QCubed-Like' instead of a thin mapping on top of jquery.
Your solution would probably work, but seems a rather ugly hack. At any rate, I used a (still rather hacky) way of doing this based on a comment on the forums by vash. I do this:
protected function update_autocompleteList() {
$strParameter = $this->AjaxAutocomplete->Text;
$aryHostingRecords = HostingRecord::QueryArray(
QQ::OrCondition(
QQ::Like(QQN::HostingRecord()->Client->FirstName, $strParameter . '%'),
QQ::Like(QQN::HostingRecord()->Client->LastName, $strParameter . '%')
),
QQ::Clause(QQ::OrderBy(QQN::HostingRecord()->Client->FirstName))
);
$result = array();
foreach($aryHostingRecords as $objHostingRecord){
$display = $objHostingRecord->Client->LastName . ", " . $objHostingRecord->Client->FirstName . " (" . $objHostingRecord->Domain . ")";
$result[] = $display;
$this->arrAutoCompleteId[$display][] = $objHostingRecord->Id;
}
$this->AjaxAutocomplete->DataSource = $result;
The key part is this:
$this->arrAutoCompleteId[$display][] = $objHostingRecord->Id;So essentially I am making an array indexed by the 'display name' with the objects id in the value. Then when my 'go' button is clicked, I retrieve the proper id with this:
QApplication::Redirect('/abcd/hosting/hostingRecordDetail?id=' . $this->arrAutoCompleteId[$this->AjaxAutocomplete->Text][0]);Well, this is pretty much exactly the first solution I proposed: whether you go directly to the database, or you cache the database results into an array and use that, the idea is the same - use the posted value from the textbox to find the ID in the back-end.
It also doesn't seam too difficult to subclass QAutocomplete and use this implementation internally, i.e. make $arrAutoCompleteId a member variable of the child class and provide the same functionality. We would definitely appreciate such a contribution.
As for the QAutoCompleteListItem, you probably are right, I probably should not have used QListItem. I guess I was trying to keep a familiar API, but I can see that since the functionality is not exactly the same, this may be somewhat confusing.
So, to summarize, I think we can do the following: If one day the official jQuery UI Autocomplete supports the feature, we will support it as well. In the mean time I think we have several solutions that can be used:
* subclass QAutocomplete and implement vash' solution
* use my javascript solution
* use the old plugin
Personally, I think we should follow your initial hunch with QListItem and continue implementing a bit more of the select box api. Since autocomplete acts like a select box, I say we write the databinder to take objects, like a select box and implement SelectedItem. Obviously it wil be implemented on the backend as some stored array in the object (similar to Vash's solution, but more configurable) , and therefore in the formstate. That way, someone could potentially get any value for the objects represented in the select box. So without any more work or mucking around with setting the specific values in the cached array, you could get SelecteItem->Id, or SelectedItem->Whatever, without changing any code.
Vartan, if I understand you correctly, I take exception with your statement. I have indeed many situations where, after the user selects a customer (for example) from the autocomplete list, I need to access the database to retrieve quite a bit of data from multiple tables to populate the form. So, for that reason, when the user selects a customer from the autocomplete list, I need the callback, with the Id of that customer so I can retrieve the necessary data. In fact, this requirement was the very reason the pipe delimiter was added to the plugin.
I suppose in a normal web application you are correct. However, a business application does require that extra data, which is not displayed in the list.
Just to pile on ; )
the argument Vartan seems to be making is not that you shouldn't need other values, but just that those other values shouldn't be sent back from the client, since you probably already knew that value when you built the datasource for the autocomplete.
I agree with that, so the question is just how and where do you store that extra info. I would prefer it to be qcubed-like and mirror how the QListBox works.
My workaround (vash's actually) puts the onus on the user to create an array that will be persisted in the formstate and you can get the value you want later.
Vartan said that since the autocomplete is a textbox, it should only send back what the user posted back to you, however that's not the full story.
Consider the normal HTML select box. The selected item only has a name and a value, so Name and Value are the only things that gets posted back, but qcubed doesn't stop there. It makes the coding process easier for the user and therefore lets you access the actual object represented as the selected item (as well as the selected value).
Why does it do that? Because otherwise you would need to write a lot of custom code for nearly every select box you created, either hitting your DB again or creating custom arrays for a lookup later.
Imagine if it didn't make accessing the object easy. You might have a select box with clients (say a small number of them, like 10). The 'display' name presented to the user is LastName, FirstName. The 'value' is the ID. That would be all well and good, and will satisfy most users. But as soon as you also need, say, the 'account number', then you have to make a work-around. You either have to:
a) get the ID Value and do another DB hit to get account number
b) build a separate array with the objects (including account number) in conjunction with building the QListBox, and then pull the info out later (it will be in the formstate)
'b' is probably a better way to do it, which is why qcubed doesn't make you do it by yourself and includes it for 'free'. You can easily access any attributes of the selected objects as represented in the QListBox.
So all I'm recommending is to extend on the work you have already done by mimic'ing more of the QListBox API.
That way you can just do a $objPerson = $this->AutoCompletePersons->SelectedValue;
And then you would have $objPerson->Account, or whatever else you need. I think this can be done while still keeping a clean separation between QAutocomplete and the underlying jquery code. But I'm not sure. You would still create the JS array to send to the browser, but you would automatically maintain an array in the object that mapped those values, sequentially, to Qcubed objects.
Cliff,
I think we are almost in agreement. The only difference is that I think the implementation you're suggesting should be done in a subclass and not in QAutocomplete itself. The reason is to keep QAutocomplete lightweight. For instance, in you example, keeping the thousands of objects as part of the control state would result in a significant increase in the memory footprint. If you think this implementation can be done cost free (i.e. you only pay if you use the feature), then by all means, let's do it in QAutocomplete. Otherwise, it should go into a subclass.
And just to answer LaCeja and clarify my position one more time:
Again, think of the situation, when the user ignored the autocomplete list, and manually entered a customer, which may or may not be an existing customer in your database. Clearly you have to handle this situation in the back-end. Which means your back-end should already be able to work without IDs coming from the browser. And this shows that the ability to post back the IDs instead of the text values is not essential. In fact, if you rely on this feature, you're most likely doing something wrong or inefficient.
All that being said, I'm not opposed to custom solutions as described above. So, please feel free to contribute.
-Vartan
Yeah, there is probably no 'cost-free' way to do it, but we do the same for QListBox. Obviously keeping an array of objects in the formstate for each list box item has a memory footprint associated with it, but most people would consider that worth the cost.
And remember, we would never be keeping thousands of objects in the autocomplete object, we would only be keeping the objects that match. So in that way, it not disimilar to a dynamic listbox. It's just one that changes dynamically on the keypress events in a linked textbox.
We would just 'get in the middle' of the DataSource assignment, storing the objects and sending on the display values to the underlying jQuery object. So you would only have thousands of objects if the bind function returned thousands of objects, but in that case it would be no different than a huge QListBox control in terms of footprints.
But, we could make it an optional feature, turned on and off with a flag. StoreObject-> True, for example.
I can code this up if you think it would be valuable. Essentially, I would just be copying the QlistBox code.
Vartan, in my case it is absolutely essential that I have the Id of the selection. This is because, when a user makes a selection, lots of things have to happen, that key off of the record selected. The customer selection is only an example. In order processing alone there are several other cases where the same situation exists. I can understand, in a "normal" web application, this would not be a requirement and/or should be handled differently. But, this is a manufacturing business application, which normally would be a fat client style, it is necessary.
In most cases, but not all, if the user enters a customer, who doesn't exist, it's considered an error. This is because the folks entering orders usually don't have the authority to enter a customer. Credit checks are required, etc. Other cases, just in order processing, would be, if the user tries to enter a part number or the name of a process that doesn't exist, they have to be stopped, until an engineer evaluates the part and its processing requirements and then creates the pieces that support order processing.
The net of it is, in many cases (but not all), I need to have the Id of the selection made by the user. It doesn't have to work exactly as it does in the old Autocomplete. If the new Autocomplete gives me direct access to the object selected, that would be even better because it would eliminate another database hit. Please let me know, how I can achieve the same results with the new Autocomplete, as I'm still very confused as to how it works.
Also, like Cliff, I will never have more than a few items that actually make it to the list. By the time the user has entered 3 or 4 characters, the list is very short. Even a long list, after typing as few as 2 characters numbers in the tens, not the hundreds.
Cliff, please do, it will definitely be valuable. We can discuss further (as part of the new ticket you create), if we should keep it in QAutocomplete, or have a new subclass. I think it will depend on the actual implementation.
@LaCeja
I think I repeated myself way too many times in this thread, but I'll give it one last shot. You said:
Great, now consider your code that does this validation, i.e. determines that the customer doesn't exist. How do you do it? Don't you look up the posted name in a cached array or directly in the database? I'm pretty sure that's what you do. If yes, then that exact same code can also give you the ID that you need.
I never said you don't need the ID. All I'm saying is that you can get the ID in the back-end, you don't need (and should not rely on) the browser sending the ID to your back-end.
-Vartan
P.S. LaCeja, in almost every post, you mention, that yours is a "business application", as opposed to our mere mortal "normal" web applications :-). First, I'm pretty sure the large majority of QCubed users are using it to develop "business" applications (I myself have like 5 QCubed applications, all running in intra-nets and all falling into that category). And second, I really don't think that in this day and age, there is that much of a difference between what you call "normal" and "business" applications. To me, as soon as the application needs a database, it's pretty much a business application.
Vartan:
I'm sorry, I certainly didn't mean to offend you or anyone else by making other applications seem insignificant. Actually, my persistence is exactly because I'm certain there are other users with similar levels of complexity.
I apologize for being so dense about this. I just want to be absolutely certain I understand correctly how it works, without making assumptions. My assumption is, the "selected" item returns a VALUE, which could be a database row object, just like in QListBox.
Thanks for your patience.
So this basically boils down to the following restriction: jQuery's AutoComplete control is just a way to fill in a textbox. That textbox has one server-accessible property: Text.
Unlike a select list item that has a name and value pair, there is only the one value to work with here.
As such, the only way to send back more than just what is displayed to the user is to change the javascript to use some other HTML control or find a way of hiding this extra text until just the moment of submission.
We _can_ do this, but it involves significant alterations to the javascript code to extend the jQuery Autocomplete code, and as such, is probably better to do via another jQuery plugin, and hence, a different QCubed control. In fact, these other jQuery plugins already exist.
Practically, any approach taken would have to support ajax-driven auto-complete results, especially since we are talking about thousands of records here, which I'm sure you don't want to pre-load to send to the client.
As such, if you want to avoid the post-submit DB hit, you would have to find a way to store new objects in the formstate during an ajax call. This may be quite challenging.
I think your best approach would be avoid storing objects in the formstate at all (otherwise, you may as well go with a listbox, since you'd have the same overhead), which means you HAVE to do another DB hit after retrieving the selected ID.
And what this means is that the only benefit the ID has is that it's potentially more unique than the display text.
But since non-unique display text would be incredibly confusing for the user, my guess is that you have unique display text as well.
So at that point the ID becomes redundant.
As much as I would like a simple to use solution, I believe that leaving the ID out of it ends up being the most efficient. :(
Vexed, so just to be clear, you're saying, if the Id is needed, it will be necessary to retain use of the existing QAjaxAutoComplete plugin. Is that correct?
Oh, no, no, it was not offensive at all, it was just besides the point and I thought it dilutes your argument and distracts from it. I also wanted to reassure you, that you're not alone, and the problems you're facing are most likely common to a lot of other applications, whether "business" or not and whether written with QCubed or not. So please, don't censure yourself because of my remark. And please continue your active participation. It's very much appreciated.
-Vartan
Vexed, I'm not sure you are correct.
QListBox is an enhancement to the the HTML select box. Although there is a nmae value pair, the value item is treated by qcubed as an arbitrary index. That index references a qcubed object value in the formstate.
With Qautocomplete, the items in the matching dropdown are just text, but they have already been implemented as QListBoxItems, which have name and value pairs already.
So all one would have to do is to store the matching objects in the formstate in the same order as they will be displayed in the client, and create an array indexed arbitrarily by that sort order. It's not perfect, of course, but it is no more overhead than a QListBox.
To restate that: Although there are thousands of records, all of those records ARE NOT being sent to the client and ARE NOT being stored in the formstate. Only the matches are stored in formstate, which is initially nothing.
You wouldn't be better with a QListBox in this case, because QAutocomplete is a QlistBox which dynamically changes it's contents and datasource, and therefore the formstate.
Imagine 1000 records. Use a listbox and you get 1000 records in the formstate. Use a (modified) QAutocomplete and initially you would have ZERO objects in the formstate. When the user types 'j', the bind function matches 23 records (for example). Now the formstate has 23 objects in it. User continues and type 'o' and the bind function matches only 3 objects. Now the formstate has 3 objects in it. The user selects item 3 in the Autocomplete dropdown. Formstate still has 3 objects in it. QAutocomplete->SelectedValue is now the object selected by the user.
If the user selects nothing and just jits their submit function, the formstate still has 3 objects in it (matched from 'jo', still) QAutocomplete->SelectedValue=null, but QAutocomplete->Text = 'jo'.
So actually it would work exactly like a QListBox, with:
--built-in dynamic entries (similar to the example page of dynamic list boxes),
--the dynamism is driven from a linked Textbox, like a combo control
--the dynamism is ajax driven
--the overhead is LESS than a QlistBox populated with all the possible options
-- the 'ListBox' _also_ has a 'Text' property if no match is selected.
The only real problem is that there is no guarantee that the displayed items in a the list that match the input have unique text, which would be used by the backend to get the proper item in the SelectedValue.
That is a similar problem with regular html select boxes, but select boxes have the the other unique value that acts to make sure the indexed object is completely specified.
Well put. Fully agreed.
I think what Vash and Cliff are suggesting is a reasonable implementation and may not be all that much challenging or costly (since you only cache the DB results that are sent as a autocomplete list items).
I know I've said this too, but it's a bit of a stretch. There are a few important advantages of the autocomplete textbox. One was already mentioned - the number of items to display is static with listbox and can be large, while it's filtered and dynamic with autocomplete, so it can be made into smaller list. Here are some more:
So I'm all for autocomplete, but I think we should allow it to do what it does best: help you enter text and send that text back to the server. The rest of the functionality should be provided by the back-end.
Cliff,
You're entirely correct, and I agree all that can be implemented. But it will only have partial utility. Consider these use cases:
* what if your form is submitted without using your UI (imagine curl, wget, or any other way of posting a form to your server)?
* what if I disabled javascript when I'm entering text, or I'm using a browser that doesn't support AJAX (lynx).
Of course you can say: "well, my application does not support these use cases". That's already restrictive, but it's fine.
But here is another use case. Imagine the data binder has a very sophisticated query. For example I'm allowing you to type either customer's first name or the last name, or both, or just initials in the textbox. Writing a data binder that can handle this is not very difficult. But what will be the key in your cached array to allow you to retrieve the ID? Will you be repeating values with all the possible keys? If not, then you'll need to provide a custom comparator that mimics what the databinder query does.
It seams that the simplest way would be to redo the query that the databinder was doing. And this goes back to my original point.
So yes your implementation will be useful in the typical scenarios, but QAutocomplete is useful in many more ways.
Actually, nevermind my last example. Your key would be the value that goes into the text box when you make the selection. You don't have to keep keys for partial matches.
Just a thought.
What if QAutocomplete were implemented as a modified QListBox, as Cliff suggests, and create a separate control to satisfy more complex requirements, as suggested by Vartan. Another benefit would be that the more common QAutocomplete requirements wouldn't be carrying the weight and complexities of supporting a custom comparator or other features.
Anyway, just thinking out loud.
That makes sense to me for the case that non-matching records are not allowed, and should provide for a clean API. If unmatched user input is still desired, the textbox base is a better conceptual match.
I think the hard part may still be finding a way of maintaining the 0-10 latest matched objects within the formstate, but you guys are right, the overhead of the actual storage of just those would be minimal.
So I propose QAutoCompleteListBox as a class name for this new restrictive control, and with an API closely matching QListBox, but with the option of an Ajax callback to set the list items.
Hello Cliff,
I'm looking for a direct replacement of QListBox that became too large.
So the Code should not have to be changed and excactly as you wrote the Object that was selected should still be completely available similar to the original Listbox.
I'm always looking at improving the drafts because this is where we should have bestpractice. Because it is the startingpoint and also what we show of.
This is how I do it:
1) Generate a Listbox on that needed field but it will not contain all Objects only the one that was previously SelectedValue. So it is completly available in Qcubed.
2)When Autocomplete is used this Listbox is hidden!
3) Now when one value is set in the Autocomplete. This hidden Listbox gets updated. And the SelectedValue is again set.
This results in no overhead. Really fast Autocomplete on the server and easiest migration from the slow QListBox to a fast Autocomplete
//In MetaControlGen.class.php
//Generate the Listbox entirely according the Qcubed paradigm BUT empty only with the currently selected value (that was previously entered)
public function lstTestlistboxObject_Create($strControlId = null, QQCondition $objCondition = null, $objOptionalClauses = null) {
$this->lstTestlistboxObject = new QListBox($this->objParentObject, $strControlId);
$this->lstTestlistboxObject->Name = QApplication::Translate('Zugehoerigkeit Abgeber Makler 2 Object');
$this->lstTestlistboxObject->AddItem(QApplication::Translate('- Select One -'), null);
$arrSelectableValues=array();
if ($this->objGeschaefte->TestlistboxObject)
$arrSelectableValues=array($this->objGeschaefte->TestlistboxObject->Id);
if (is_null($objCondition)) $objCondition = QQ::In(QQN::Personen()->Id, $arrSelectableValues);
$objTestlistboxObjectCursor = Personen::QueryCursor($objCondition, $objOptionalClauses);
while ($objTestlistboxObject = Personen::InstantiateCursor($objTestlistboxObjectCursor)) {
$objListItem = new QListItem($objTestlistboxObject->__toString(), $objTestlistboxObject->Id);
if (($this->objGeschaefte->TestlistboxObject) && ($this->objGeschaefte->TestlistboxObject->Id == $objTestlistboxObject->Id))
$objListItem->Selected = true;
$this->lstTestlistboxObject->AddItem($objListItem);
}
return $this->lstTestlistboxObject;
}
//In Test_edit.php
//This Method should be excecuted when a entry was selected from Autocomplete eg "QAjaxAction onselect"!!
//Use the autocomplete to set the value THEN fill the listbox -> SelectedValue is available with all normal features.
//So no rewrite is needed to change existing programs using QListbox to Autocomplete
protected function btnRerender_lstTestlistboxObject_Click($strFormId, $strControlId, $strParameter) {
$this->lstTestlistboxObject->RemoveAllActions('onmouseover');
$intCurrentlySelectedValue=$this->lstTestlistboxObject->SelectedValue;
$this->lstTestlistboxObject->RemoveAllItems(); // so we do not have the Selected Value in there 2 times later
$intCurrentControlId=$this->lstTestlistboxObject->ControlId;
//NOW do
//pseudocode: GET the selecte value from Qlistbox (eventually we can also have multiple values here for n:m mapping)
$objTestlistboxObject = Personen::QueryArray(QQ::In(QQN::Personen()->Id, $arrSelectableValues));
if ($objTestlistboxObject) foreach ($objTestlistboxObject as $objCurrentObject) {
$objListItem = new QListItem($objCurrentObject->__toString(), $objCurrentObject->Id);
$this->lstTestlistboxObject->AddItem($objListItem);
//pseudocode: SET selected value
}
}
I think this is very similar to what was discussed here.
Interestingly I have this already implemented in Qcodo Templates but without autocomplete (instead with loading the Qlistboxes one by one on demand), so it can be generated automatically.
It would be great to get this into Qcubed as the QAutocompleteListbox is excactly what I'm looking for.