Version 0.7.10 et 0.7.11

  • Nouveaux thèmes (cliquer sur config et sélectionner un thème)

  • Refonte de la sauvegarde pour éviter le bind

Version 0.7.9

Example 1. Nouveau filtre filterFieldExpressionReverse
Filtre filterFieldExpressionReverse
def parentParts() {
    UiFilterSpecifier f = new UiFilterSpecifier().sec PlmFreeCadPart, {         (1)
        PlmFreeCadLink link = new PlmFreeCadLink()
        filterFieldExpressionReverse(link.part_,                                (2)
                true,                                                           (3)
                new FilterExpression(null as Object, Operator.NE, link.part_))  (4)
    }
    taackUiService.show(new UiBlockSpecifier().ui {
        tableFilter(plmFreeCadUiService.buildPartFilter(), plmFreeCadUiService.buildPartTable(null, f), {
            menuIcon ActionIcon.GRAPH, this.&model as MC
        })
    }, buildMenu())
}
1 Nous construisons un filtre pour PlmFreeCadPart
2 Nous souhaitons lister les assemblages parents, ceux pour lesquels le champ PlmFreeCadLink part ne pointe pas vers le PlmFreeCadPart listé
3 true correspond à notIn
4 Littéralement : nous voulons tous les PlmFreeCadPart qui ne sont pas des enfants
HQL généré
select
    distinct sc  ,sc.dateCreated
from PlmFreeCadPart sc
where
    sc.id NOT IN (select auo.part.id from PlmFreeCadLink auo where auo.part IS NOT NULL)    (1)
    and sc.nextVersion IS NULL
order by sc.dateCreated DESC
1 Clause where ajoutée par l’expression filterFieldExpressionReverse

Version 0.7.8

  • Version de maintenance

Version 0.7.7

Example 2. Nouveau composant calendrier
Affichage en tableau
def calendarTable(TaackCalendarParams calendarParams) {
    UiTableSpecifier t = taackCalendarUiService.calendarUiTable(calendarEvents.collect {    (1)
        new TaackCalendarEventAdapter(it.key, it.value)
    }.iterator() as Iterator<TaackCalendarEvent>, calendarParams)
    taackUiService.show new UiBlockSpecifier().ui {
        table t
    }, RootController.buildMenu()
}
1 Créer un tableau à partir d’un itérateur TaackCalendarEvent. Utilisation du patron Adapter pour convertir tout objet en TaackCalendarEvent.
news calendar table view
Affichage standard
def calendarEntries(TaackCalendarParams calendarParams) {
    taackUiService.show taackCalendarUiService.calendarBlock(CalendarController.&fromCustom as MC, calendarEvents.collect {
        new TaackCalendarEventAdapter(it.key, it.value)
    }.iterator() as Iterator<TaackCalendarEvent>, calendarParams), RootController.buildMenu()}
news calendar regular view

Version 0.7.6

  • Amélioration de l’éditeur personnalisé

  • Grails 7.2.0

Version 0.7.5

show grid layout

Example 3. Disposition Show en grille (Grid Layout)
Disposer un Show sur une grille adaptative via ShowLayout.GRID
new UiShowSpecifier().ui ShowLayout.GRID, {                      (1)
    fieldLabeled item.ref_
    fieldLabeled item.name_
    fieldLabeled item.itemStatus_
    fieldLabeled item.gtin_
    fieldLabeled Style.SPAN_TWO, item.labeling_                  (2)
    fieldLabeled Style.BOLD + Style.SPAN_FULL, item.description_ (3)
}
1 Passer ShowLayout.GRID pour disposer les champs sur une grille à ajustement automatique au lieu de verticalement dans une colonne. La valeur par défaut est ShowLayout.LIST.
2 Chaque champ occupe une cellule de grille par défaut. Style.SPAN_TWO / Style.SPAN_THREE peuvent être utilisés pour permettre aux cellules de s’étendre sur plusieurs colonnes.
3 Style.SPAN_FULL fait s’étendre un champ sur toute la ligne. Les étendues se combinent avec n’importe quel autre style via + (ici avec Style.BOLD).

Notez que la grille ajuste automatiquement autant de colonnes que la largeur du conteneur le permet, chacune d’au moins 220px de large (grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) sur la classe taackShowGrid, le minimum pouvant être redéfini dans votre CSS).

Example 4. Nouveau remplacement du contenu d’une modale
Remplacer tout le contenu d’une modale supérieure
@Transactional
def doComplexStuff() {
    boolean isDone = false

    // doComplexStuff

    taackUiService.show(new UiBlockSpecifier().ui {
        replaceModal {
            custom("<h1> ${isDone ? 'Done.' : 'Not Done.'} </h1>")
        }
    })
}
Example 5. Nouveau : sauvegarder ou fermer la modale et rafraîchir
Code
@Transactional
@Secured(['ROLE_ADMIN', 'ROLE_CONSOLATOR_ADMIN'])
def saveLlmRag() {
    taackSaveService.saveOrCloseModalAndRefresh(LlmRag)     (1)
}
1 Si plusieurs modales sont empilées, il est désormais possible de rafraîchir la modale située sous la dernière, laquelle sera fermée.

Version 0.7.4

  • Utilisation de Flying Saucer pour produire des PDF, évitant les problèmes liés à la licence AGPL d’iText

  • Amélioration de l’interaction utilisateur sur les diagrammes

  • DSL de diagramme :

    • Ajout du nouveau type de diagramme timeline

    • Fourniture de DiagramOption pour personnaliser le graphique

  • Colonnes de tableau figées (Sticky Columns)

Example 6. Colonnes de tableau figées (Sticky Columns)
Figer les colonnes les plus à gauche via TableOption
TableOption tableOption = new TableOption.TableOptionBuilder()
        .stickyColumns(2).build() (1)

new UiTableSpecifier().ui tableOption, {
    header {
        sortableFieldHeader p.ref_
        sortableFieldHeader p.name_
        // ... any number of columns ...
    }
}
1 Nombre de colonnes les plus à gauche à figer. Elles restent visibles lorsque le tableau défile horizontalement.

Notez que les tableaux utilisant des colonnes figées n’auront pas de contrainte de largeur et utiliseront à la place la classe CSS taack-sticky-table qui limite la largeur maximale de chaque colonne à 12rem.

Version 0.7.2

Example 7. Kan-ban avec glisser-déposer (Drag & Drop)
Spécification Kanban sans filtre
new UiKanbanSpecifier().ui {
    ReminderStatus.values().each { ReminderStatus status ->             (1)
        column(this.&changeStatus as MC, [newStatus: status.name()]) {  (2)
            header status.name()                                        (3)
            reminders.grep { Reminder t -> t.status == status }
                     .sort { -it.lastUpdated.getTime() }
                     .each { Reminder rIt ->
                card(rIt.selfObject_) {                                 (4)
                    cardField rIt.title_, null, Style.BOLD + Style.BLUE
                    cardFieldRaw this.getReminderDoersHtml(rIt),
                        new Style('', 'position: absolute; right: 3px; top: 12px;')
                    cardAction ActionIcon.SHOW * IconStyle.SCALE_DOWN,
                        ReminderController.&showReminder as MethodClosure, rIt.id
                    cardField rIt.priority_
                }
            }
        }
    }
}
1 Itérer sur les colonnes
2 Définir une colonne
3 Nom d’en-tête de cette colonne
4 Créer la carte avec cardField, cardFieldRaw et cardAction
screenshot kanban
Figure 1. Kan-Ban
Example 8. Édition d’objets dans un tableau
Déclarer le bloc rowQuickEdit
rowQuickEdit(CrmController.&applyLeadLine as MC, ll.id) {           (1)
    rowColumn {
        rowFieldEdit ll.salePrice_                                  (2)
        rowFieldEdit ll.qty_                                        (2)
        rowField nf.format(tot), style                              (3)
    }
}
1 rowQuickEdit déclare quelle action appeler en cas d’édition en ligne
2 rowFieldEdit modifie la valeur dans un champ de saisie OU affiche le contenu si non autorisé
3 Les autres champs sont recalculés lors des modifications
Définition de applyLeadLine
@Transactional
def applyLeadLine() {
    LeadLine ll = new LeadLine()
    taackSaveService.saveThenReloadOrRenderErrors
                    LeadLine, ll.qty_, ll.salePrice_            (1)
}
1 Lors de la sauvegarde, lister les champs qui peuvent être mis à jour uniquement dans le bloc rowQuickEdit
screenshot inline edit
Figure 2. Édition en ligne avec retour d’erreur localisé
Example 9. Nouvelle sérialisation de filtre
Déclarer un filtre
UiFilterSpecifier createReminderFilter(FieldInfo... leftField) {
    Reminder r = new Reminder()
    User u = new User()
    new UiFilterSpecifier(leftField).ui Reminder, {                 (1)
        section Reminder, {                                         (2)
            filterField r.assignee_, u.username_
            filterField r.status_
            filterField r.priority_
            filterField r.title_
        }
    }
}
1 leftField liste les champs qui pointent vers un Reminder dans l’objet englobant
2 Nouvelle section avec un argument de classe pour calculer l’i18n automatiquement
Joindre un filtre
UiFilterSpecifier createReminderLinkFilter(FieldInfo... leftField) {
    ReminderLink rl = new ReminderLink()
    new UiFilterSpecifier(leftField).ui(ReminderLink, {
        section tr('reminderLink.label'), {
            filterField rl.kind_
        }
    }).join(createReminderFilter(rl.reminder_))                     (1)
}
1 Le champ reminder est un Reminder, nous réutilisons le filtre de la classe Reminder.
screenshot filter join
Figure 3. Filtres joints
Example 10. Nouvelle option de mise en page rowCol
        taackUiService.show createModalForm(reminder, {
            section {
                field reminder.title_
                rowCols 3, {                                        (1)
                        field reminder.dueDate_
                        field reminder.status_
                        field reminder.priority_
                }
                ajaxField reminder.assignee_, CrewController.&selectUserM2O as MC
                field reminder.body_
            }
1 Évite row, puis col, col, col …​
screenshot form rowcol
Figure 4. rowCol dans un formulaire
Example 11. Nouvelle instruction de rafraîchissement de bloc poke
taackUiService.show(new UiBlockSpecifier().ui {
    table statsService.buildTable()                                 (1)

    row {
        poke p1, {                                                  (2)
            col BlockSpec.Width.HALF, {
                diagram(statsService.&buildChart1 as MethodClosure)
            }
        }
        poke p2, {                                                  (2)
            col BlockSpec.Width.HALF, {
                diagram(statsService::buildChart2 as MethodClosure)
            }
        }
    }
}
1 En cliquant sur un tableau, nous pouvons rafraîchir d’autres blocs
2 poke p1 et p2 sont des booléens. S’ils valent true, les blocs à l’intérieur du bloc poke seront rafraîchis
Example 12. Accordéon (Accordion)
accordion {
    accordionItem('Filter') {
        form CrmDashboardUiService.filterForm(filter)
    }
}
Example 13. scrollPanel
scrollPanel('512px') {
    UiTableSpecifier t
    boolean seeSalesmen = params.boolean('seeSalesmen')
    boolean seeTop = params.boolean('seeTop')
    if (seeTop) {
        t = crmDashboardUiService.topSalesmen(leads)
    } else if (seeSalesmen) {
        t = this.crmDashboardUiService.topCountries(leads)
    } else {
        t = crmDashboardUiService.topCustomer(leads)
    }
    table(t) {
        menu 'Salesmen', this.&group as MC, [seeTop: true, seeSalesmen: false]
        menu 'Customers', this.&group as MC, [seeTop: false, seeSalesmen: false]
        menu 'Countries', this.&group as MC, [seeTop: false, seeSalesmen: true]
    }
}
screenshot scrollpanel

Version 0.7.1

  • Correction de l’i18n avec la tâche bootRun lorsque le rechargement à chaud (hot-reload) est activé

Quoi de neuf dans la version 0.7.1

Version 0.7.0

  • Grails 7, Gradle 8.4, Java 24, Groovy 4.0.28 …​

  • Amélioration des performances : démarrage plus tôt de la transmission de la page et consommation mémoire réduite par page

  • Découpage de taack-ui en taack-ui, taack-ui-jdbc, taack-ui-pdf, taack-ui-ssh, taack-ui-search, taack-ui-asciidoc. Un serveur minimal utilisant uniquement taack-ui devrait produire un Jar d’environ 70 Mo

  • Menus contextuels pour les champs et les classes (voir Exemple de code pour menu contextuel)

  • Assistant de notification : permet de marquer les lignes non lues d’un tableau via le setter de filtre setLastReadingDate (voir Créer un filtre marquant les entrées non lues)

  • Changements sur SolrSearch : le contexte des objets doit être enregistré de manière générique (voir Enregistrer la façon dont un objet sera affiché)

  • Nouvelle personnalisation des diagrammes via des builders, facilitant l’intégration des diagrammes dans les e-mails et les PDF (voir Builder de diagramme)

  • Éditeur Asciidoc avec capacités de conversion lors du copier/coller (voir Éditeur Asciidoc)

  • Amélioration du rendu des diagrammes dans les e-mails et les PDF

  • TaackUiConfiguration n’est plus initialisé via application.yml, ses membres sont statiques et peuvent être modifiés dans server/grails-app/init/…​/Application.groovy (voir TaackUiConfiguration)

  • Il est désormais possible d’être rappelé lors d’un événement de glisser-déposer (drop) sur un tableau ou une cellule de tableau (voir Déposer sur un tableau (Drop on Table), Déposer sur des cellules (Drop on Cells), la colonne de ligne est une zone de dépôt)

Exemple de code pour menu contextuel
TaackUiService.registerContextualMenuClosure(                           (1)
    new Lead().leadStatus_,
    new UiMenuSpecifier().ui { Long id ->                               (2)
        label 'Change Status'
        Lead.read(id)?.leadStatus?.transitionsTo()?.each {
            menu it.toString(),                                         (3)
                CrmController.&changeLeadStatus as MC,
                [id: id, leadStatus: it.toString()]
    }
})
1 Enregistrer un menu contextuel pour un champ
2 Utiliser une closure MenuSpecifier standard (comme pour n’importe quel menu)
news contextual menu on field
Figure 5. Résultat d’un clic droit sur le statut d’un Lead
Créer un filtre marquant les entrées non lues
TaackFilter.FilterBuilder tf = taackFilterService.getBuilder(Item)
        .setMaxNumberOfLine(10)
        .setSortOrder(TaackFilter.Order.DESC, i.dateCreated_)

tf.setLastReadingDate(lastAccessDateFromUser)   (1)
1 Les objets les plus récents seront en gras, voir Lignes non lues où lastUpdated de l’objet > lastAccessDateFromUser
news notif
Figure 6. Lignes non lues où lastUpdated de l’objet > lastAccessDateFromUser
Enregistrer la façon dont un objet sera affiché
TaackGormClassRegisterService.register(
        new TaackGormClass(ItemRange.class).builder
                .setShowMethod(Bp2Controller.&showRange as MethodClosure)   (1)
                .build(),
        new TaackGormClass(Item.class).builder
                .setShowMethod(Bp2Controller.&showItem as MethodClosure)    (1)
                .setShowLabel({ Long id ->                                  (2)
                    def i = Item.read(id)
                    return "${i.name} ${i.ref ?: ''} ($id)"
                })
                .setTypeLabel({ Long id ->                                  (3)
                    return TaackUiService.tr('default.item.label')
                }).build()
)
1 Comment afficher l’objet
2 Libellé personnalisé
3 Type pour le facettage
Builder de diagramme
new UiDiagramSpecifier().ui {
    bar {
        labels 'xLabel1', 'xLabel2', 'xLabel3', 'xLabel4'
        dataset 'dataset1', 1.0, 2.0, 3.0, 4.0
        dataset 'dataset2', 6.0, 1.0, 2.0, 4.0

        option DiagramOption.builder
            .setClickAction(Rma2Controller.&editClaim as MethodClosure)
            // "diagramAction" was moved to here
            .showDataCount()
            // The numbers are hidden by default. Use this option if need to show
            .hideLegend()
            // The legends are shown by default. Use this option if need to hide
            .setResolution(DiagramOption.DiagramResolution.DEFAULT_4K)
            // Default resolution is [SVG: 960x480, PNG: 2160x1080].
            // Allow to set to 540p, 720p, 1080p, 2K, 1440p, 4K
            .setKeyColors(Color.RED, Color.BLUE)
            // Allow to self-define the colors for each dataset
            // (If not set, there are by default 7 colors)
            .setTitle("Test Chart") // Diagram title
            .build()
    }
}
Éditeur Asciidoc
//...
EditorOption.EditorOptionBuilder editor = EditorOption.getBuilder()
editor.addSpanRegexes(TaackBaseAsciidocSpans.spans)                 (1)
editor.addSpanRegexes(TaackAsciidocTable.spans)                     (1)
editor.addSpanRegexes(TaackAsciidocPlantUML.spans)                  (1)
editor.onDrop(this.&dropEditor as MC, cmsPage.id)                   (2)
fieldEditor cmsPage.bodyContent_, editor.build()                    (3)
innerFormAction this.&previewBody as MC                             (4)
//...

@Transactional
def dropEditor() {                                                  (2)
    CmsPage page = CmsPage.get(params.long('cmsPage'))
    params.remove('cmsPage')
    String past = params.get('onpaste')
    if (past) {                                                     (5)
        render taackAsciidocService.convertFromHtml(past)
    } else {                                                        (6)
        final List<MultipartFile> mfl = (request as MultipartHttpServletRequest).getFiles('filePath')
        final mf = mfl.first()

        if ([AttachmentContentType.SHEET_ODS.mimeType,
             AttachmentContentType.LO_TEXT.mimeType
            ].contains(mf.contentType)) {                           (7)
            render taackAsciidocService.convert(page, mf.inputStream)
        } else {                                                    (8)
            CmsImage cmsImage = taackSaveService.save(CmsImage)
            if (cmsImage && page) {
                cmsImage.cmsPage = page
            }
            render "image::${cmsImage.originalName}[]"
        }
    }
}
1 Ajouter des extensions de coloration syntaxique (essentiellement une expression régulière et une classe pour colorer le code)
2 Callback lorsqu’un fichier est déposé, ou lorsqu’un collage depuis une autre page ou application est effectué
3 Le champ dans le formulaire
4 Action d’aperçu optionnelle
5 Collage d’un contenu HTML. Le contenu sera converti en Asciidoc
6 Partie dépôt de fichier
7 Le fichier est un document ODS ou Writer : il sera converti en Asciidoc et les images seront importées
8 Le fichier est une image : elle sera sauvegardée dans le CMS.
taackAsciidocService utilise le service TaackEditorService de taack-ui-asciidoc, voir form DSL
TaackUiConfiguration
@CompileStatic
class TaackUiConfiguration {
    static String defaultTitle = 'Taack'
    static String logoFileName = 'logo-taack-web.svg'
    static int logoWidth = 70
    static int logoHeight = 60

    static boolean fixedTop = false
    static boolean hasMenuLogin = true
    static boolean outlineContainer = false
    static String bgColor = '#05294c'
    static String fgColor = '#eeeeee'
    static String bodyBgColor = '#fff'

    static String home = System.getProperty('user.home')
    static String root = home + '/intranetFiles'
    static String taack = home + '/taack'
    static String resources = taack + '/resources'
    static String javaPath = '/usr/bin/java'
    static String plantUmlPath = home + '/plantuml-1.2022.7.jar'
    static String solrUrl = 'http://localhost:8983/solr/taack'
    static Boolean disableSecurity = false
}
Application.groovy
@CompileStatic
class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        TaackUiConfiguration.disableSecurity = true
        TaackUiConfiguration.hasMenuLogin = false
        GrailsApp.run(Application, args)
    }
}
Déposer sur un tableau (Drop on Table)
        TableOption tableOption = new TableOption.TableOptionBuilder()
                .onDropAction(this.&testDrop as MC).build()             (1)

        new UiTableSpecifier().ui tableOption, {
            header {
                sortableFieldHeader cb.position_
                sortableFieldHeader cb.subsidiary_
                label 'Page OR Menu Entry'
            }
1 Action appelée en cas de dépôt quelque part sur le tableau.
Déposer sur des cellules (Drop on Cells), la colonne de ligne est une zone de dépôt
cellDropAction(CmsController.&testCellDrop as MC)                       (1)
rowColumn {
    rowField cp.title_
    rowField 'test drop'
}
1 Action appelée en cas de dépôt quelque part sur la cellule.

Version 0.6.1

Utilisation des "Diagram Action"
new UiDiagramSpecifier().ui {
    bar {
        labels 'date1', 'date2', 'date3'
        dataset 'Stuff1', 3.0, 4.0, 5.0

        diagramAction this.&clickDiagram as MC, id, (1)
        [optionalParam: 'value']                    (2)
    }
}

def clickDiagram() {
    println(params)
    // [id: 123456, dataset: "Stuff1",
    // optionalParam: "value"                       (3)
    // x: "date1", y: "3.0"]
}
1 Diagram Action
2 Possibilité de passer une map
3 Les paramètres d’action contiennent label, value, dataset name et map
Utilisation des "Static Helpers"
import org.codehaus.groovy.runtime.MethodClosure as MC

import static taack.ui.TaackUi.createMenu           (1)

@Override
UiMenuSpecifier editorCreate() {
    createMenu {                                    (2)
        menu this.&createFromTemplate as MC
        menuIcon
            CollaboraIcons.WRITER,
            this.&createFromTemplate as MC,
            [collaboraApp: CollaboraApp.WRITER]
    }
}
1 Import statique
2 Appel statique createMenu, raccourci pour new UiMenuSpecifier().ui, d’autres raccourcis incluent createModal, retournant UiBlockSpecifier

Version 0.6

  • Animation de chargement

  • Zoom / défilement sur les diagrammes

  • Info-bulles (tooltips) sur les diagrammes

  • Sélection multiple sur les tableaux (voir doc/DSLs/filter-table-dsl.html)

  • Chargement à la demande (lazy loading) des onglets

  • Amélioration de la pagination

  • Optimisation du rendu, en évitant les dessins inutiles

  • Évite les cas où les blocs étaient dessinés deux fois

  • Amélioration de la disposition de recherche

  • Prise en charge des BigDecimal dans les tableaux, affichés selon la locale de l’utilisateur

  • WiP : Éditeur Asciidoc WYSIWYG simple

Version 0.5.7

  • Nettoyage du code DSL show, déprécie le passage d’un paramètre objet à UiShowSpecifier().ui

  • Éditeur WYSIWYG Asciidoctor initial

    • Support du glisser-déposer d’images et de fichiers

  • Nouveaux DSL de diagrammes

    • timeSeries

    • areaChart

    • bubbleChart

  • Sécurité

    • Assainissement des données affichées par défaut (utiliser fieldRaw pour éviter l’assainissement)

    • Vérification de l’accès sur tous les points d’entrée

  • Corrections de bugs et mise à jour des dépendances

Remplacer field <html code> par fieldRaw <html code>

Version 0.5.6 (correction de bugs de la version 0.5.5)

screenshot news sorting
Figure 7. Les en-têtes de colonne indiquent les directions du tri
Comment conserver le contexte en cliquant sur un tableau
def showPart(PlmFreeCadPart part, Long partVersion, Boolean isHistory) {(1)
    taackUiService.show(
            plmFreeCadUiService.buildFreeCadPartBlockShow(
                    part, partVersion, false, isHistory),               (2)
            buildMenu(),
            "isHistory")                                                (3)
}
1 isHistory est un paramètre d’action
2 isHistory est utilisé lors du dessin du bloc ; il faut le retransmettre pour dessiner exactement la même disposition de bloc, en conservant le contexte
3 La clé isHistory est passée comme dernier argument de taackUiService.show. Vous pouvez passer plusieurs clés à conserver.

Version 0.5.4

Version 0.5.3

  • Correction des cases à cocher (checkboxes) dans les formulaires

  • Autorise les alias en TQL pour les colonnes de formule

  • Nettoyage du code et incrémentation des versions de dépendances

Version 0.5.2

  • Le client JDBC est désormais aussi une extension AsciidoctorJ

  • Ajout de getters aux champs de domaine accessibles via JDBC

  • Ajout du DSL TQL et TDL (Taack Display Language) pour décrire comment afficher les données requêtées (soit sous forme de tableau, soit de diagramme en barres)

  • Restauration de l’étiquetage manuel sur les menus

  • Plus de fonctionnalités sur le DSL de diagramme (merci à Chong et ZhenQing)

  • Meilleure personnalisation

TQL et TDL (Taack Display Language)
select
    u.rawImg,
    u.username,
    u.manager.username
from User u
where u.dateCreated > '2024-01-01' and u.manager.username = 'admin';
--
table rawImg as "Pic", username as "Name", manager as "Manager"
news table
Figure 8. Résultats

Version 0.5.1

Remplacement de TaackPlugin
@PostConstruct
void init() {
    TaackUiEnablerService.securityClosure(
        this.&securityClosure,
        CrewController.&editUser as MC,
        CrewController.&saveUser as MC)
    TaackAppRegisterService.register(
        new TaackApp(
            CrewController.&index as MC,                    (1)
            new String(
                this.class
                    .getResourceAsStream("/crew/crew.svg")  (2)
                    .readAllBytes()
            )
        )
    )
}
1 Point d’entrée
2 Icône
Remplacement des Charts : Diagrams
private static UiDiagramSpecifier d1() {
    new UiDiagramSpecifier().ui {
        bar(["T1", "T2", "T3", "T4"] as List<String>, false, {
            dataset 'Truc1', [1.0, 2.0, 1.0, 4.0]
            dataset 'Truc2', [2.0, 0.1, 1.0, 0.0]
            dataset 'Truc3', [2.0, 0.1, 1.0, 1.0]
        }, DiagramTypeSpec.HeightWidthRadio.ONE)
    }
}
PDF contenant des diagrammes
printableBody {
    diagram(d1(), BlockSpec.Width.HALF)
    diagram(d2(), BlockSpec.Width.HALF)
}
news diagram
Figure 9. Diagramme en barres empilées

Version 0.5.0

Basé sur Bootstrap

  • Thématisable

    • Schéma de couleurs issu de votre bureau

    • Taille des composants ajustable

  • Plus facile à étendre

Temps de chargement amélioré

  • Moins de ressources (assets)

  • CSS et JS raccourcis

Principaux changements dans le code

Nouveau DSL de mise en page

  • Même mise en page pour forms et blocks

    • col et row

    • tabs et tab

With uniquement pour col

Menu DSL pour les blocs

  • Le DSL de menu remplace les actions

  • Plus besoin d’utiliser des icônes

  • Les menus sont traduits automatiquement

Code plus simple

Permet :

  • Une maintenance plus facile

  • Une contribution plus accessible

Version 0.4.2

À paraître…​ cette version devrait comporter quelques changements intéressants (en cassant parfois de l’ancien code)

  • Améliorer la hiérarchie DSL

    • champs cachés en haut uniquement pour la lisibilité

    • pas de passage de paramètres redondants dans le formulaire

    • pas de passage de paramètres redondants dans le filtre

    • filterField uniquement sous section

    • champ de niveau supérieur du formulaire uniquement dans l’en-tête

  • hook pour les champs de formulaire afin d’afficher les M2M proprement

  • hook pour enregistrer un filtre d’objet typique

  • Améliorer la restauration d’état

  • Correction du regroupement / des arborescences de tableaux avec pagination

  • À déterminer

Version 0.4.1

screenshot news menu 0.4.1
Figure 10. Mise à jour de la disposition des menus
Code de la disposition des menus
private UiMenuSpecifier buildMenu(String q = null) {
    new UiMenuSpecifier().ui {
        menu CrewController.&index as MC
        menu CrewController.&listRoles as MC
        menu CrewController.&hierarchy as MC
        menuIcon ActionIcon.CONFIG_USER, this.&editUser as MC
        menuIcon ActionIcon.EXPORT_PDF, this.&downloadBinPdf as MC
        menuSearch this.&search as MethodClosure, q
        menuOptions(SupportedLanguage.fromContext())            (1)
    }
}
1 Le choix de la langue se trouve à droite de la barre de recherche, et d’autres énumérations peuvent être ajoutées
Guide de débogage Kotlin JS
$ cd infra/browser/client                             (1)
$ ./gradlew browserDevelopmentRun                     (2)
$ vi intranet/server/grails-app/conf/application.yml  (3)
# Uncomment line bellow
# client.js.path: 'http://localhost:8080/client.js'

# Then your browser should show Kotlin code !
1 Déplacez-vous vers le dossier client où le code JS est généré
2 Lancez un serveur servant client.js et client.js.map …​
3 Modifiez votre fichier intranet application.yml
Nouvelle simplification du DSL Solr (plus besoin de libellés)
@PostConstruct
private void init() {
    taackSearchService.registerSolrSpecifier(this,
            new SolrSpecifier(User,
                CrewController.&showUserFromSearch as MethodClosure,
                this.&labeling as MethodClosure, { User u ->
        u ?= new User()
        indexField SolrFieldType.TXT_NO_ACCENT, u.username_
        indexField SolrFieldType.TXT_GENERAL, u.username_
        indexField SolrFieldType.TXT_NO_ACCENT, u.firstName_
        indexField SolrFieldType.TXT_NO_ACCENT, u.lastName_
        indexField SolrFieldType.POINT_STRING, "mainSubsidiary", true, u.subsidiary?.toString()
        indexField SolrFieldType.POINT_STRING, "businessUnit", true, u.businessUnit?.toString()
        indexField SolrFieldType.DATE, 0.5f, true, u.dateCreated_
        indexField SolrFieldType.POINT_STRING, "userCreated", 0.5f, true, u.userCreated?.username
    }))
}

Version 0.4.0

  • Plus de paginate dans les tableaux. Voir Nouvelle utilisation de iterate

  • Pas de list, mais un iterate prenant une closure comme paramètre, avec une approche de type builder pour passer les arguments

  • Les menus sont désormais étiquetés automatiquement (utilisez lang=test dans l’URL pour traduire les menus). Voir Nouveau code menu

  • Plus de paramètre isAjax dans les tableaux …​ Voir Nouveau code rowAction

  • Remplacement de rowLink par rowAction [i18n_isAjax]

  • Pas de libellé nécessaire sur rowAction dans les tableaux. Voir Nouveau code rowAction

  • Plus besoin d'`ajaxBlock` pour les tableaux, les formulaires et les tableFilters

  • formAction n’a plus de paramètre isAjax

  • formAction n’a plus de paramètre i18n obligatoire

  • form n’a plus de paramètre i18n obligatoire, l’i18n est basé sur le nom de l’action courante

  • L’action de bloc n’a plus de paramètre i18n obligatoire, l’i18n est basé sur l’action cible

  • L’action de bloc n’a plus de paramètre isAjax obligatoire

Nouvelle utilisation de iterate
iterate(taackFilterService.getBuilder(Role)                     (1)
        .setMaxNumberOfLine(20)                                 (2)
        .setSortOrder(TaackFilter.Order.DESC, u.authority_)     (3)
        .build()) { Role r, Long counter ->
            row {
                rowColumn {
                    rowField r.authority
                    if (hasSelect)
                        rowAction
                            ActionIcon.SELECT * IconStyle.SCALE_DOWN,
                            CrewController.&selectRole as MC
                            r.id                                (4)
                }
            }
        }
1 itérer
2 Spécifier max suffit à déclencher la pagination s’il y a plus de lignes
3 Remplace l’ancien modèle inefficace pour décrire le tri et l’ordre initiaux
4 Plus de paramètres i18n et isAjax
Nouveau code menu
private UiMenuSpecifier buildMenu(String q = null) {
    UiMenuSpecifier m = new UiMenuSpecifier()
    m.ui {
        menu CrewController.&index as MC        (1)
        menu CrewController.&listRoles as MC
        menu CrewController.&hierarchy as MC
        menuSearch this.&search as MethodClosure, q
    }
    m
}
1 Aucun paramètre i18n
Nouveau code rowAction
if (hasActions) {
    rowColumn {
        rowAction ActionIcon.EDIT * IconStyle.SCALE_DOWN, this.&roleForm as MC, r.id (1)
    }
}
1 Pas de paramètre i18n, pas de dernier paramètre isAjax

Version 0.3.9

Aucune mise à jour depuis trop longtemps, l’hibernation touche à sa fin. Cette version offre :

  • Grails 6.2.0

  • Groovy 3.0.21

  • Mise à niveau de diverses dépendances …​ (voir Changelog)