Search
Search is a SOLoist sample application that shows a prototypical search form for searching the object space according to the user's input criteria.
The filtering criteria are entered in the input controls in the top part of the form. The result set is returned and shown in the grid at the bottom.
Searching without any of the fields filled in will return all objects of the class Person from the object space. Entering parameter values will filter the result set. Selecting one or more Bank Advisers from the list will search only for the Persons that have the selected objects as their Bank Advisers (linked over the myBankAdvisers association end).
In the result table, additional options can be selected:
- the columns to be displayed or hidden
- the page size
- whether the size of the entire result set will be calculated and displayed or not (this may have a great performance impact for very large data sets and complex queries).
Clicking on any column header of the result table will sort the results in the ascending/descending order of the values in that column.
The results can be exported to an Excel table.
A double-click on a Person object icon shows the form with the details of that object.
Live example
UML Model
Business Logic Code
package rs.sol.sampleapps.search; import java.util.Arrays; import rs.sol.sampleapps.Person; import rs.sol.soloist.server.guiconfiguration.components.GUISearchResultComponent.OrderByAnother; import rs.sol.soloist.server.guiconfiguration.components.GUISearchResultComponent.Parameter; import rs.sol.soloist.server.guiconfiguration.components.GUISearchResultComponent.Result; import rs.sol.soloist.server.uml.queries.AssocEndTerm; import rs.sol.soloist.server.uml.queries.AttributeTerm; import rs.sol.soloist.server.uml.queries.ObjectTerm; import rs.sol.soloist.server.uml.queries.SimpleQueryDefinition; import rs.sol.soloist.server.uml.queries.builder.QueryBuilder; public class PersonQueryBuilder extends QueryBuilder { private static final SimpleQueryDefinition prototypeQuery; @Parameter // @Parametar annotation means that QueryBuilder expects a parameter value from the element component named as the annotated string @Result("Name") // @Result annotation means that the column "Name" will be in the result table public static final String NAME = "NAME"; @Result("Person") @OrderByAnother(NAME) // @OrderByAnother - objects of Person will be ordered by the name attribute public static final String PERSON = "PERSON"; @Result("Age") public static final String AGE = "AGE"; @Parameter @Result("Gender") public static final String GENDER = "GENDER"; @Parameter @Result("Date of birth") public static final String DATE_OF_BIRTH = "DATE_OF_BIRTH"; @Parameter @Result("Is married?") public static final String IS_MARRIED = "IS_MARRIED"; @Parameter public static final String OLDER_THAN = "OLDER_THAN"; @Parameter public static final String YOUNGER_THAN = "YOUNGER_THAN"; @Parameter public static final String BANK_ADVISOR = "BANK_ADVISOR"; public PersonQueryBuilder() { super(prototypeQuery); contributions.addAll(Arrays.asList( new ContributePrefixMatch(NAME), new ContributeEqual(IS_MARRIED), new ContributeEqual(GENDER), new ContributeEqual(DATE_OF_BIRTH), new ContributeGreaterOrEqual(OLDER_THAN), new ContributeLessOrEqual(YOUNGER_THAN), new ContributeIn(BANK_ADVISOR) )); includeInResultOnce( // Columns in the result table, in this order PERSON, GENDER, DATE_OF_BIRTH, AGE, IS_MARRIED ); } @Override public SimpleQueryDefinition buildCountQuery() { require(PERSON); return super.buildCountQuery(); } @Override public SimpleQueryDefinition buildQuery() { require(PERSON); return super.buildQuery(); } static { ObjectTerm root = new ObjectTerm(Person.FQ_TYPE_NAME).as(PERSON); new AttributeTerm(root, Person.PROPERTIES.name).as(NAME); new AttributeTerm(root, Person.PROPERTIES.gender).as(GENDER); new AttributeTerm(root, Person.PROPERTIES.age).as(AGE); new AttributeTerm(root, Person.PROPERTIES.dateOfBirth).as(DATE_OF_BIRTH); new AttributeTerm(root, Person.PROPERTIES.isMarried).as(IS_MARRIED); new AttributeTerm(root, Person.PROPERTIES.age).as(OLDER_THAN); new AttributeTerm(root, Person.PROPERTIES.age).as(YOUNGER_THAN); new AssocEndTerm(root, Person.PROPERTIES.myBankAdvisers).as(BANK_ADVISOR); prototypeQuery = new SimpleQueryDefinition(root).from(root); } }
GUI Code
package rs.sol.sampleapps.search; import rs.sol.sampleapps.BankAdviser; import rs.sol.sampleapps.Gender; import rs.sol.sampleapps.Person; import rs.sol.sampleapps.gui.PersonSearch; import rs.sol.soloist.helpers.init.DefaultContextInit; import rs.sol.soloist.helpers.init.Initializer; import rs.sol.soloist.helpers.init.InitializerFailedException; import rs.sol.soloist.modelreader.Repository; import rs.sol.soloist.server.builtindomains.builtindatatypes.Boolean; import rs.sol.soloist.server.builtindomains.builtindatatypes.Date; import rs.sol.soloist.server.builtindomains.builtindatatypes.Integer; import rs.sol.soloist.server.builtindomains.builtindatatypes.Text; import rs.sol.soloist.server.guiconfiguration.components.GUIApplicationComponent; import rs.sol.soloist.server.guiconfiguration.components.GUIButtonComponent; import rs.sol.soloist.server.guiconfiguration.components.GUIDeckComponent; import rs.sol.soloist.server.guiconfiguration.components.GUILabelComponent; import rs.sol.soloist.server.guiconfiguration.components.GUIPanelComponent; import rs.sol.soloist.server.guiconfiguration.components.GUISearchPanelComponent; import rs.sol.soloist.server.guiconfiguration.construction.GUIComponentBinding; import rs.sol.soloist.server.guiconfiguration.elementcomponents.GUIEdit; import rs.sol.soloist.server.guiconfiguration.elementcomponents.GUIInput; import rs.sol.soloist.server.guiconfiguration.layout.FlowLayout; import rs.sol.soloist.server.guiconfiguration.layout.TableLayoutData; import rs.sol.soloist.server.guiconfiguration.nonvisualcompoments.GUIFindAllInstancesSAPComponent; import rs.sol.soloist.server.guiconfiguration.style.GUIBindingsFeature; import rs.sol.soloist.server.guiconfiguration.style.GUIContext; import rs.sol.soloist.server.guiconfiguration.style.GUIObjectSetting; import rs.sol.soloist.server.guiconfiguration.style.GUIPictureFeature; import rs.sol.soloist.server.guiconfiguration.style.GUITextFeature; import rs.sol.soloist.server.server.SoloistServiceServlet; public enum Search implements Initializer { INSTANCE; @Override public void init() throws InitializerFailedException { GUIApplicationComponent application = new GUIApplicationComponent(); application.setName("SearchSample"); SoloistServiceServlet.registerApplication(application); GUIPanelComponent root = GUIPanelComponent.createFlow(application); GUILabelComponent title = GUILabelComponent.create(root, "Search"); title.setStyle("titleStyle"); GUIPanelComponent topPanel = GUIPanelComponent.createFlow(root); topPanel.setStyle("topPanel"); GUIDeckComponent mainDeck = GUIDeckComponent.create(topPanel); GUIPanelComponent wrapPanel = GUIPanelComponent.createFlow(mainDeck); GUISearchPanelComponent searchForm = GUISearchPanelComponent.create(wrapPanel, new FlowLayout()); // GUISearchPanelComponent binds element components implicitly between the search form and the query builder based on the components' names searchForm.setStyle("form searchForm"); GUIPanelComponent table = GUIPanelComponent.createTable(searchForm); table.setStyle("table"); table.setSize("100%", null); int row = 0; // name needs to be provided, in this case PersonQueryBuilder.NAME constant GUIInput.createField(table, Text.CLASSIFIER, row++, 1).setName(PersonQueryBuilder.NAME); GUIInput.createField(table, Repository.getRepository().getEnumeration(Gender.FQ_TYPE_NAME), row++, 1).setName(PersonQueryBuilder.GENDER); GUIInput.createField(table, Date.CLASSIFIER, row++, 1).setName(PersonQueryBuilder.DATE_OF_BIRTH); GUIInput.createField(table, Boolean.CLASSIFIER, row++, 1).setName(PersonQueryBuilder.IS_MARRIED); GUIInput.createField(table, Integer.CLASSIFIER, row++, 1).setName(PersonQueryBuilder.OLDER_THAN); GUIInput.createField(table, Integer.CLASSIFIER, row++, 1).setName(PersonQueryBuilder.YOUNGER_THAN); row = 0; GUILabelComponent.create(table, "Name", row++, 0).setStyle("formLabel"); GUILabelComponent.create(table, "Gender", row++, 0).setStyle("formLabel"); GUILabelComponent.create(table, "Date of birth", row++, 0).setStyle("formLabel"); GUILabelComponent.create(table, "Is married?", row++, 0).setStyle("formLabel"); GUILabelComponent.create(table, "Older than", row++, 0).setStyle("formLabel"); GUILabelComponent.create(table, "Younger than", row++, 0).setStyle("formLabel"); GUILabelComponent.create(wrapPanel, "Search by Bank Advisers:"); GUISearchPanelComponent searchList = GUISearchPanelComponent.create(wrapPanel, new FlowLayout()); GUIFindAllInstancesSAPComponent advisersSource = GUIFindAllInstancesSAPComponent.create(searchList, BankAdviser.CLASSIFIER); GUIInput bankAdvisors = GUIInput.createList(searchList); GUIComponentBinding.create(advisersSource.opValue(), bankAdvisors.ipCollection()); bankAdvisors.setName(PersonQueryBuilder.BANK_ADVISOR); bankAdvisors.setUpperBound(-1); bankAdvisors.setSize("250px", "154px"); bankAdvisors.setStyle("listWidget"); GUIButtonComponent btnAll = GUIButtonComponent.create(wrapPanel, "Select All"); GUIButtonComponent btnNone = GUIButtonComponent.create(wrapPanel, "Select None"); GUIComponentBinding.create(btnAll.opClick(), bankAdvisors.ipSelectAll()); GUIComponentBinding.create(btnNone.opClick(), bankAdvisors.ipUnselectAll()); GUIPanelComponent buttonPanel = GUIPanelComponent.createFlow(wrapPanel); buttonPanel.setStyle("searchButtons"); GUIButtonComponent searchButton = GUIButtonComponent.create(buttonPanel, "Search"); searchButton.setStyle("submitButton"); GUIButtonComponent resetButton = GUIButtonComponent.create(buttonPanel, "Reset"); resetButton.setStyle("submitButton"); GUIPanelComponent searchTable = GUIPanelComponent.createFlow(wrapPanel); searchTable.setStyle("searchPanel"); PersonSearch searchResult = new PersonSearch(); searchTable.add(searchResult); searchResult.setDoInitialSearch(false); searchForm.addDynamicBindingDest(searchResult); // binds all fields in search form with corresponding query builder searchList.addDynamicBindingDest(searchResult); GUIComponentBinding.create(searchButton.opClick(), searchResult.ipSearch()); GUIComponentBinding.create(resetButton.opClick(), searchForm.ipReset()); GUIComponentBinding.create(resetButton.opClick(), searchResult.ipClearContents()); GUIComponentBinding.create(searchButton.opClick(), searchResult.ipSearch()); GUIComponentBinding.create(resetButton.opClick(), searchForm.ipReset()); GUIComponentBinding.create(resetButton.opClick(), searchResult.ipClearContents()); GUIComponentBinding.create(wrapPanel.opRelay2(), searchResult.ipSearch()); GUIPanelComponent personDetails = GUIPanelComponent.createTable(mainDeck); personDetails.setStyle("searchDetails"); GUIPanelComponent detailsTable = GUIPanelComponent.createTable(personDetails); application.setContext(createContextAndStyles(detailsTable)); createDetails(detailsTable, wrapPanel); } /** * Person details editors */ private void createDetails(GUIPanelComponent personDetails, GUIPanelComponent backTo) { int row = 0; GUILabelComponent.create(personDetails, "Name: ", row, 0); GUIEdit nameEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.name, row++, 1); GUILabelComponent.create(personDetails, "Gender: ", row, 0); GUIEdit genderEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.gender, row++, 1); GUILabelComponent.create(personDetails, "Age: ", row, 0); GUIEdit ageEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.age, row++, 1); GUILabelComponent.create(personDetails, "Date of birth: ", row, 0); GUIEdit dateOfBirthEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.dateOfBirth, row++, 1); GUILabelComponent.create(personDetails, "Height [m]: ", row, 0); GUIEdit heightEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.height, row++, 1); GUILabelComponent.create(personDetails, "Is married: ", row, 0); GUIEdit isMarriedEditor = GUIEdit.createField(personDetails, Person.PROPERTIES.isMarried, row++, 1); GUIComponentBinding.create(personDetails.opRelay1(), nameEditor.ipElement()); GUIComponentBinding.create(personDetails.opRelay1(), genderEditor.ipElement()); GUIComponentBinding.create(personDetails.opRelay1(), ageEditor.ipElement()); GUIComponentBinding.create(personDetails.opRelay1(), dateOfBirthEditor.ipElement()); GUIComponentBinding.create(personDetails.opRelay1(), heightEditor.ipElement()); GUIComponentBinding.create(personDetails.opRelay1(), isMarriedEditor.ipElement()); GUIButtonComponent backButton = GUIButtonComponent.create(personDetails, "Back"); backButton.setLayoutData(TableLayoutData.create(row++, 0)); GUIComponentBinding.create(backButton.opClick(), backTo.ipShow()); } private GUIContext createContextAndStyles(GUIPanelComponent personDetails) { GUIContext context = new GUIContext(); DefaultContextInit.getRoot().addContext(context); // how will Person objects look like in the GUI? GUIObjectSetting person = GUIObjectSetting.create(context, Person.CLASSIFIER); GUITextFeature.createName(person, Person.PROPERTIES.name); GUIPictureFeature.createSmallIcon(person, "images/icons/person.png"); GUIBindingsFeature bf = GUIBindingsFeature.create(person); // doubleClick signal carries a Person object GUIComponentBinding.create(bf.opDoubleClick(), personDetails.ipRelay1()); // double-clicking on a person in the result table will show the person's details GUIComponentBinding.create(bf.opDoubleClick(), personDetails.ipShow()); return context; } }