A better(?) user selector form helper widget -- my start
Permalink
I find that the selectUser() widget in the form helper isn't very great. It uses the user manager, which not all users have access to, and is probably a bit overly complicated for end-users who need to do any sort of assigning to users. Also, the multiple user selection widget doesn't work at all (in that it doesn't handle users that are already selected). So I created a new set and thought I'd share. Your mileage may vary; while this works, I'm not (currently) planning on packaging this for plug-and-play. Consider it a starting point for developers.
Basically, I'm using jquery ui's autocomplete functionality and showing a text input box where you type the user name (or other info to be searched). This box is called [your key]-selector and, when a user is properly selected, it populates a hidden element called [your key] with the user ID. This means that the ID be read from the form in a normal fashion.
In the single user selector, the username stays in the box and a green checkmark is added to show that the user is "set". If the username is not valid or not selected through the autocomplete dropdown, then the box is cleared.
The multiple selector works similarly except that the [your key] hidden element will contain an exploded array (eg "1,2,3") and there's a UL where the usernames are listed. Whether or not you choose a valid user, the selector is emptied out.
See the attached image for an example.
The following sets up the form elements and is added to a class that extends the form helper:
The following is the contents of user_search which is in my amplify_lmt/tools directory (you'll need to change the hardcoded link in the JS to reflect a different tools directory):
The following is in my package's JS file and sets up the elements. I find it easier to run this on every page load based on classes (if there is no matching elements, nothing happens), than to include a bunch of JS within the widget's HTML. At the bottom are a few helper functions that I use to manage the "1,2,3" value in the hidden element as if it were an array.
And the following is some CSS to make everything look pretty:
Basically, I'm using jquery ui's autocomplete functionality and showing a text input box where you type the user name (or other info to be searched). This box is called [your key]-selector and, when a user is properly selected, it populates a hidden element called [your key] with the user ID. This means that the ID be read from the form in a normal fashion.
In the single user selector, the username stays in the box and a green checkmark is added to show that the user is "set". If the username is not valid or not selected through the autocomplete dropdown, then the box is cleared.
The multiple selector works similarly except that the [your key] hidden element will contain an exploded array (eg "1,2,3") and there's a UL where the usernames are listed. Whether or not you choose a valid user, the selector is emptied out.
See the attached image for an example.
The following sets up the form elements and is added to a class that extends the form helper:
public function userWidgetSingle($key, $group_name, $value) { $id = $name = $query = ""; if (is_numeric($value)) { $u = UserInfo::getByID($value); $id = $u->uID; $name = $u->getAttribute('first_name') . ' ' . $u->getAttribute('last_name'); $class = 'ac-selected'; } if ($group_name != '') { $query = '?group=' . urlencode($group_name); } $html = "<input type='text' id='$key-selector' name='$key-selector' value='$name' class='ccm-input-text userselector single $class' /><input type='hidden' id='$key-query' name='$key-query' value='$query' /><input type='hidden' id='$key' name='$key' value='$id' />"; return $html; } public function userWidgetMultiple($key, $group_name, $values) {
Viewing 15 lines of 35 lines. View entire code block.
The following is the contents of user_search which is in my amplify_lmt/tools directory (you'll need to change the hardcoded link in the JS to reflect a different tools directory):
<?php defined('C5_EXECUTE') or die("Access Denied."); header('Content-type: application/json'); //SECURITY -- check that user is allowed to search through users Loader::model('user_list'); $js = Loader::helper('json'); $ul = new UserList(); foreach (explode(' ', $_GET['term']) as $kw) { $ul->filterByKeywords($kw); } foreach (explode(',', $_GET['notUsers']) as $uid) { $ul->excludeUsers($uid); } if ($_GET['group']) { $ul->filterByGroup($_GET['group']);
Viewing 15 lines of 22 lines. View entire code block.
The following is in my package's JS file and sets up the elements. I find it easier to run this on every page load based on classes (if there is no matching elements, nothing happens), than to include a bunch of JS within the widget's HTML. At the bottom are a few helper functions that I use to manage the "1,2,3" value in the hidden element as if it were an array.
$('.userselector.single').autocomplete({ source: function(req, resp) { var id = this.element.attr('id'); var qs = $('#' + id.substr(0, id.length - 9) + '-query').val(); $.getJSON('/index.php/tools/packages/amplify_lmt/user_search' + qs, req, function (data) { resp(data); }) }, minLength: 2, delay: 300, autoFocus: true, select: function(e, ui) { $('#' + e.target.id.substr(0, e.target.id.length - 9)).data('ac-val', ui.item.value).val(ui.item.ID); $(e.target).addClass('ac-selected').parents('form').validate().form(); }
Viewing 15 lines of 90 lines. View entire code block.
And the following is some CSS to make everything look pretty:
form.crud ul.userselector { list-style: none; padding: 0; } ul.userselector { position: relative; } ul.userselector li img { position: absolute; right: 0; cursor: pointer; } li.dummy { display: none; }
Viewing 15 lines of 18 lines. View entire code block.
What do you mean about a block? Whether for pages or users, I don't see much point in having this standalone -- it seems only valuable as something you use when developing another package/block or single functionality.
Yeah, I meant to say helper.
Nice work. I am planning on fixing the user selector for multiple users in concrete5 core, but you're right, it uses the user search that many users won't have access to. This custom widget would be great for certain things like assigning users (which, for example, we do on concrete5.org). We went this approach on concrete5.org and it's a good one
Andrew, have you added this widget (or your version) to github?
jShannon, thank you for this.
jShannon, thank you for this.
James,
Does this code work? I think you presented a great idea that needs address, but I don't see it working when compared to the jQuery Autocomplete function documentation. Am I misunderstanding something? Have you actually used this code?
Thanks for your help and insight.
Rick
Does this code work? I think you presented a great idea that needs address, but I don't see it working when compared to the jQuery Autocomplete function documentation. Am I misunderstanding something? Have you actually used this code?
Thanks for your help and insight.
Rick
Hi.
Yes, this does work (or, rather, I should say that it worked fine for me, with 5.4.1). I didn't paste the complete files, though, as there was a lot of other, unrelated stuff that was mixed in that's not as interesting to the community.
If you want to debug this yourself, you should look at it as several discrete parts that can be tackled separately:
1. The userWidget...() functions should be in a helper. When called from your code (such as a block or something, you should be able to see in the HTML source something that matches up with the php code (a few input type=hidden's, etc). Note that this isn't simply a drop-in to a standard c5 install... once the code is in there you have to call it explicitly.
2. If the javascript has been included, then it'll instantiate the user selector and, when you start typing, something should happen. Notice that in the js code, a tools path is hardcoded in... this will definitely have to be changed. But you can make sure that the js is making the appropriate calls by using the js debug console of most browsers.
3. The tool code has to be installed somewhere. You can call it directly to make sure that it's returning a list of users as appropriate. You can also watch the js debug console to see what's getting returned.
James
Yes, this does work (or, rather, I should say that it worked fine for me, with 5.4.1). I didn't paste the complete files, though, as there was a lot of other, unrelated stuff that was mixed in that's not as interesting to the community.
If you want to debug this yourself, you should look at it as several discrete parts that can be tackled separately:
1. The userWidget...() functions should be in a helper. When called from your code (such as a block or something, you should be able to see in the HTML source something that matches up with the php code (a few input type=hidden's, etc). Note that this isn't simply a drop-in to a standard c5 install... once the code is in there you have to call it explicitly.
2. If the javascript has been included, then it'll instantiate the user selector and, when you start typing, something should happen. Notice that in the js code, a tools path is hardcoded in... this will definitely have to be changed. But you can make sure that the js is making the appropriate calls by using the js debug console of most browsers.
3. The tool code has to be installed somewhere. You can call it directly to make sure that it's returning a list of users as appropriate. You can also watch the js debug console to see what's getting returned.
James
I just worked with Rick to get this working and have a few comments:
* remember to remove the .form().validate(); lines if you're not using jquery's validator
* remember to wrap in jquery's $(function() { xxx }); so that the js is executed after the dom is loaded
* this won't work (out of the box) with attributes because the attribute field name (and thus id) comes in the form of xxx[yyy][zzz], which breaks jquery. You'll have to escape the square brackets in several places (but not all).
James
* remember to remove the .form().validate(); lines if you're not using jquery's validator
* remember to wrap in jquery's $(function() { xxx }); so that the js is executed after the dom is loaded
* this won't work (out of the box) with attributes because the attribute field name (and thus id) comes in the form of xxx[yyy][zzz], which breaks jquery. You'll have to escape the square brackets in several places (but not all).
James
I was just getting ready to delv in and do this very same thing except for pages.
perhaps a block where you could pre-designate a parent page and have a "find-while-you-type" selector.
very nice.
Chad