(js) Improve sg-search directive

Will now respect the "listRequiresDot" source parameter and uses
ng-messages to show an error if the minimum number of characters is not
reached.

Fixes #438, #3464
pull/201/head
Francis Lachapelle 2016-02-24 21:30:33 -05:00
parent 4b816677b0
commit 70fbeab27a
12 changed files with 75 additions and 31 deletions

View File

@ -1035,6 +1035,10 @@ not work for entries in this source and thus, freebusy lookups.
|If set as an address book, the human identification name of the LDAP
repository
|listRequiresDot (optional)
|If set to `YES`, listing of this LDAP source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitely typing a single dot.
Defaults to `YES` when unset.
|ModulesConstraints (optional)
|Limits the access of any module through a constraint based on an LDAP
attribute; must be a dictionary with keys `Mail`, and/or `Calendar`,

2
NEWS
View File

@ -17,6 +17,8 @@ Enhancements
- [web] improved confirm dialogs for deletions
- [web] allow resources to prevent invitations (#3410)
- [web] warn when double-booking attendees and offer force save option
- [web] list search now displays a warning regarding the minlength constraint
- [web] loading an LDAP-based addressbook is now instantaneous when listRequiresDot is disabled (#438, #3464)
- [eas] now support EAS MIME truncation
Bug fixes

View File

@ -104,3 +104,6 @@
"Loading" = "Loading";
"No such user." = "No such user.";
"You cannot (un)subscribe to a folder that you own!" = "You cannot (un)subscribe to a folder that you own!";
/* Error message display bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters";

View File

@ -615,11 +615,6 @@
);
}
- (int) minimumSearchLength
{
return [[[context activeUser] domainDefaults] searchMinimumWordLength];
}
@end /* UIxPageFrame */
@interface UIxSidenavToolbarTemplate : UIxComponent

View File

@ -339,6 +339,8 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoGCSFolderK]], @"isEditable",
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoContactSourceFolderK]
&& ![currentFolder isPersonalSource]], @"isRemote",
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoContactSourceFolderK]
&& [currentFolder listRequiresDot]], @"listRequiresDot",
acls, @"acls",
urls, @"urls",
nil];

View File

@ -86,6 +86,10 @@
/* SoUser */
- (NSString *) shortUserNameForDisplay;
/* Common defaults and settings */
- (int) minimumSearchLength;
- (NSString *) minimumSearchLengthLabel;
/* labels */
- (NSString *) labelForKey:(NSString *)_key;
- (NSString *) commonLabelForKey:(NSString *)_key;

View File

@ -22,6 +22,7 @@
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSUserDefaults.h> /* for locale strings */
#import <Foundation/NSValue.h>
#import <NGObjWeb/SoObjects.h>
#import <NGObjWeb/WOResponse.h>
@ -509,6 +510,22 @@ static SoProduct *commonProduct = nil;
return [[context activeUser] login];
}
/* Common defaults and settings */
- (int) minimumSearchLength
{
return [[[context activeUser] domainDefaults] searchMinimumWordLength];
}
- (NSString *) minimumSearchLengthLabel
{
NSDictionary *defaults;
defaults = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: [self minimumSearchLength]]
forKey: @"minimumSearchLength"];
return [defaults keysWithFormat: [self commonLabelForKey: @"Enter at least %{minimumSearchLength} characters"]];
}
/* labels */
- (NSString *) labelForKey: (NSString *) _str

View File

@ -274,17 +274,21 @@
</md-button>
</div>
<!-- search mode -->
<div class="md-toolbar-tools sg-toolbar-secondary"
<form name="searchForm" class="md-toolbar-tools sg-toolbar-secondary"
layout="row"
ng-show="addressbook.mode.search"
sg-search="addressbook.selectedFolder.$filter(searchText, { search: searchField })">
sg-search="addressbook.selectedFolder.$filter(searchText, { search: searchField })"
sg-allow-dot="addressbook.selectedFolder.listRequiresDot">
<md-button class="md-icon-button"
sg-search-cancel="addressbook.cancelSearch()"
label:aria-label="Back">
<md-icon>arrow_back</md-icon>
</md-button>
<md-input-container class="md-flex" md-no-float="md-no-float">
<input name="folderSearch" type="search" label:placeholder="Search"/>
<input name="folderSearch" type="search" var:minlength="minimumSearchLength" label:placeholder="Search"/>
<div ng-messages="searchForm.folderSearch.$error" ng-show="searchForm.folderSearch.$dirty">
<div ng-message="minlength"><var:string value="minimumSearchLengthLabel"/></div>
</div>
</md-input-container>
<md-input-container flex="25">
<md-select>
@ -293,7 +297,7 @@
<md-option value="organization"><var:string label:value="Organization"/></md-option>
</md-select>
</md-input-container>
</div>
</form>
</md-toolbar>
<!-- multiple selection mode -->
@ -362,7 +366,7 @@
</span>
</md-subheader>
<md-subheader ng-hide="addressbook.service.$query.value">
<span ng-switch="addressbook.selectedFolder.isRemote">
<span ng-switch="addressbook.selectedFolder.listRequiresDot">
<span ng-switch-when="1">
<var:string label:value="Start a search to browse this address book"/>
</span>

View File

@ -116,10 +116,12 @@
</md-button>
</div>
<!-- search mode -->
<div class="md-toolbar-tools sg-toolbar-secondary"
layout="row"
ng-show="mailbox.mode.search"
sg-search="mailbox.selectedFolder.$filter(null, [{ searchBy: searchField, searchInput: searchText }])">
<form class="md-toolbar-tools sg-toolbar-secondary"
name="searchForm"
layout="row"
ng-show="mailbox.mode.search"
sg-search="mailbox.selectedFolder.$filter(null, [{ searchBy: searchField, searchInput: searchText }])"
sg-allow-dot="false">
<md-button class="sg-icon-button"
sg-search-cancel="mailbox.cancelSearch()"
label:aria-label="Back">
@ -137,7 +139,7 @@
<md-option value="body"><var:string label:value="Entire Message"/></md-option>
</md-select>
</md-input-container>
</div>
</form>
</md-toolbar>
<!-- multiple-selection mode -->

View File

@ -457,10 +457,12 @@
</md-button>
</div>
<!-- search mode -->
<div class="md-toolbar-tools sg-toolbar-secondary"
layout="row"
ng-show="list.mode.search"
sg-search="list.component.$filter(list.componentType, { value: searchText, search: searchField })">
<form class="md-toolbar-tools sg-toolbar-secondary"
name="searchForm"
layout="row"
ng-show="list.mode.search"
sg-search="list.component.$filter(list.componentType, { value: searchText, search: searchField })"
sg-allow-dot="false">
<md-button class="md-icon-button"
sg-search-cancel="list.cancelSearch()"
label:aria-label="Back">
@ -475,7 +477,7 @@
<md-option value="entireContent"><var:string label:value="Entire content"/></md-option>
</md-select>
</md-input-container>
</div>
</form>
</md-toolbar>
<!-- multiple-selection mode -->

View File

@ -53,11 +53,11 @@
<!-- MAIN CONTENT ROW -->
<var:component-content />
<!-- JAVASCRIPT IMPORTS -->
<script type="text/javascript">
var ApplicationBaseURL = '<var:string value="modulePath" />';
var ResourcesURL = '<var:string value="applicationPath" />.woa/WebServerResources';
var minimumSearchLength = <var:string value="minimumSearchLength" />;
var minimumSearchLengthLabel = '<var:string value="minimumSearchLengthLabel" />';
<var:if condition="isUIxDebugEnabled">
var DebugEnabled = true;
</var:if>
@ -121,6 +121,7 @@
<script type="text/javascript" rsrc:src="js/vendor/angular-animate.min.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-sanitize.min.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-aria.min.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-messages.min.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-material.js"><!-- space --></script>
<script type="text/javascript" rsrc:src="js/vendor/angular-ui-router.min.js"><!-- space --></script>

View File

@ -60,6 +60,13 @@
return function postLink(scope, iElement, iAttr, controller) {
var compiledButtonEl = iElement.find('button');
// Retrive the form and input names to check the form's validity in the controller
controller.formName = iElement.attr('name');
controller.inputName = inputEl.attr('name');
// Associate the sg-allow-dot parameter (boolean) to the controller
controller.allowDot = $parse(iElement.attr('sg-allow-dot'))(scope);
// Associate callback to controller
controller.doSearch = $parse(iElement.attr('sg-search'));
@ -99,7 +106,6 @@
minLength = angular.isNumber($window.minimumSearchLength)? $window.minimumSearchLength : 2;
// Controller variables
vm.previous = { searchText: '', searchField: '' };
vm.searchText = null;
// Model options
@ -113,20 +119,22 @@
// Method to call on data changes
vm.onChange = function() {
if (typeof vm.searchText !== 'undefined' && vm.searchText !== null) {
if (vm.searchText != vm.previous.searchText || vm.searchField != vm.previous.searchField) {
if (vm.searchText.length > minLength || vm.searchText.length === 0 || vm.searchText == '.') {
// doSearch is the compiled expression of the sg-search attribute
vm.doSearch($scope, { searchText: vm.searchText, searchField: vm.searchField });
}
vm.previous = { searchText: vm.searchText, searchField: vm.searchField };
}
var form = $scope[vm.formName],
input = form[vm.inputName],
rawSearchText = input.$viewValue;
if (vm.allowDot && rawSearchText == '.' || form.$valid) {
if (rawSearchText == '.')
// Ignore the minlength constraint when using the dot operator
input.$setValidity('minlength', true);
// doSearch is the compiled expression of the sg-search attribute
vm.doSearch($scope, { searchText: rawSearchText, searchField: vm.searchField });
}
};
// Reset input field when cancelling the search
vm.cancelSearch = function() {
vm.previous = { searchText: '', searchField: '' };
vm.searchText = null;
};
}