Transcript Aikau Dynamic UI Generation
Dynamic UI Generation with Alfresco Share and Aikau
Kevin Dorr Sr. Solutions Engineer Alfresco Channel Americas
Agenda
• What • Why • Where • How
What is Aikau?
A Interesting Story
Aikau History
UI / UX is Terrifying and Complex
Custom UI Development, Really?
Yeah Really!
Some Things People Say to Me
Share would be great if it just had a way to… Share seems just like Sharepoint , except then you find out it’s not.
Share is way more than I want to expose my users to… I really liked that Workdesk thing that you guys had. Why did you get rid of it again?
I want my user experience to look exactly like this: I’m porting my product so that it will work with Share. So how do I do that?
Example Aikau User Interfaces
Ok, So Where is this Stuff, Anyway
Using Aikau and Aikau Tools
The Aikau Screen Builder
Displaying an Aikau Page
• Stand Alone Page Webscripts /share/page/dp/ws/{webscript URL} /share/page/site/{site}/dp/ws/{ws URL} • Hybrid Pages (Share header and footer) /share/page/hdp/ws/{ws URL} • Hybrid Remote Pages (from Repo) /share/page/site/{site}/p/{page name} /share/page/hrp/p/{page name}
Adding an Aikau Page to a Site
• Hook to an Existing Page • I will show an example • Pass the Current Context to a New Page • We will publish an example • Or, Create a New Standalone Page and Configure it In through the Site Menu • Same as it was
Aikau Resources
• Blogs!
• Search for Dave Draper • Summit Videos • Last year = 100 level • This year = 200 level • Ole Hejlskov’s Tutorial • http://ohej.github.io/alfresco-tutorials/tutorial/aikau/tutorial.html#where-to-find more-information • Documentation!
• http://dev.alfresco.com/resource/docs/jsdoc-haiku/ • Email Me! • • We are interested in hearing about your projects!
Just How Flexible Is It?
Aikau Widgets, Styling, and Internationalization
Alfresco Widget Library
Adding Widgets
• Easy to Add New Widgets • Please open source if appropriate!
• Commerical opportunity!
• Dojo Widgets can be Used Directly • I showed an example of extending a Dojo widget last year • But… You can Configure in ANY JS Library!
• I will show a jQuery example • We will publish an Angluar example • Let us know what other examples you would like!
Styling and Themes
• Widgets can Have an Individual CSS File • Add the CSS to the META-INF directory • Theme support is limited right now • Widgets need to use a common convention in order for themes to work • Engineering has not focused on this yet • But, it will be coming!
Text and Internationalization
• Best Practice is to Use a Properties File • Add all of your UI strings as properties • Get them using the msg.get() function • This Allows the Interface to be Internationalized using Filename Extensions • A properties file with a “fr” extension will be selected if the interface is in French mode • Just like standard Java
The Nitty Gritty
“Wax Your Board!”
To Review…
• page-name .get.desc.xml (Webscript Descriptor) • • page-name .get.js page-name .get.html.ftl
(WebScript Controller) (Freemarker Template) • page-name .get.properties (Optional Text Strings) Files need to be in the same directory Anywhere under site-webscripts is ok
To Review… Page Descriptor
To Review… Freemarker Template
<@processJsonModel group="share"/>
To Review… the JS Page Controller
{ } "widgets": [ { "id": "SET_PAGE_TITLE", "name": "
alfresco/header/SetTitle
", "config": { "title": "Alfresco Summit Demo" } }, { "name": "
alfresco/layout/AlfSideBarContainer
", "config": { "initialSidebarWidth": "300" } } ]
Page Layout
• Hand Code the JSON • I showed an example of extending a Dojo widget last year • Programmatically Generate the JSON • Interesting examples in the Faceted Search page • Use the Page Creator Tool!
• Very functional – also configurable • Great start on a page • However, not all of the widgets are on the palette
Page Webscript Basics
• Define your JSON Any Way you Want • Once again, interesting examples in the Faceted Search page • Add in Any Additional Logic You Need • It’s a Javascript file!
• There are lots of built in Alfresco functions, Dojo function, etc.
• You can dynamically splice page components together – I will show an example
How Do I Get Data on the Page?
• Services are Exposed as Components • In the Page Builder Tool Palette • Look at the search code for examples • Also Special Data Widgets • Quick and Dirty Data Structures (QuaDDS) • Stores data structures you define as JSON in a folder in the repo in the Data Dictionary • 1001 uses!
Capturing Events
• Events Use Pub/Sub Paradigm • They will bubble up through the DOM • You can also limit the scope – Dojo stuff • Pub/Sub is by Name • Easy to generate or subscribe to an event • You can marshal a data package to go along with the event • I will show some examples
Fully Dynamic Page Creation Example “Eddie Would Go!”
Demonstration Scenario
• Acme Manufacturing Technical Support • Target: CSRs and Support Engineers • Handle Problem Reports (mostly from Wiley E. Coyote) • Extend the Basic Search Page • Searches on Acme Case Specific Types • Tune Searches for User Types • Needs to be done Yesterday!!!
That Sinking Feeling
Leveraging an Existing Page
What We Will Do
• Add a Toggle to the Search Page • Toggle between the default keyword search and a “special” search page • Add a Form on the Special Search Page • Define the form with the Page Creation Tool • Search for Particular Content Types • Provide Different Search Criteria by User
Infrastructure and Code Setup
Setup to Extend the Search Form
• Need to Define 2 Files: • A Module Extension Definition • The JavaScript for the Aikau Page/Form Extension • Packaging • Module extension in alfresco/site data/extensions • Code in alfresco/site-webscripts/… • Deliver as a jar (for development) or AMP (for production)
Maven 2.0 Package Setup
Define the Module Extension File
File: alfresco/site-data/extensions/acme-special-search-extension.xml
Adding the Search Toggle Button
Adding the Search Toggle Button
Dynamically Adding to the Search Page
• Identify the Code to Extend • Adding New Functionality to the Page • Identify the Form to Add to • Add Widget Definitions • Handle the Toggle Event • Using a Special Aikau Trick!
• Deploy and See What Happens!
Finding the Search Page Code
Finding the Search Page Code
Finding the Search Page Code
Search Page Code Files
Web Script: org/alfresco/share/pages/faceted-search/faceted-search.get
Script Properties Id: Short Name: org/alfresco/share/pages/faceted-search/faceted-search.get
Aikau Faceted Search Page […] File: org/alfresco/share/pages/faceted-search/faceted-search.get.desc.xml
<@processJsonModel group="share"/> File: org/alfresco/share/pages/faceted-search/faceted-search.get.properties
Locating the ID to Hook Widgets To
File: org/alfresco/share/pages/faceted-search/faceted-search.get.js
var scopeSelection = { id: "FCTSRCH_TOP_MENU_BAR", name: "alfresco/layout/LeftAndRight", config: { widgets: [ { name: "alfresco/html/Label", config: { label: msg.get("faceted-search.scope.label") } }, { name: "alfresco/menus/AlfMenuBar", config: { widgets: [ { id: "FCTSRCH_SCOPE_SELECTION_MENU", name: "alfresco/menus/AlfMenuBarSelect", <….>
Building the Page Toggle
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Create links for hiding and showing the advanced search...
var showAdvancedSearchLink = { name: "alfresco/renderers/PropertyLink", align: "right", config: { visibilityConfig: hideRegularSearch, currentItem: { label: "Display Acme Case Mgt Search" }, propertyToRender: "label", renderSize: "small", useCurrentItemAsPayload: false, publishTopic: "ALF_SHOW_ADVANCED_SEARCH", publishPayloadType: "CONFIGURED", publishPayload: { show: true } } };
Building the Page Toggle
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
var hideAdvancedSearchLink = { name: "alfresco/renderers/PropertyLink", align: "right", config: { visibilityConfig: showAdvancedSearch, currentItem: { label: "Display Regular Search" }, propertyToRender: "label", renderSize: "small", useCurrentItemAsPayload: false, publishTopic: "ALF_SHOW_ADVANCED_SEARCH", publishPayloadType: "CONFIGURED", publishPayload: { show: false } } }; // Add the new widgets to the main stack...
var topMenuBar = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_TOP_MENU_BAR"); if (topMenuBar && topMenuBar.config && topMenuBar.config.widgets) { topMenuBar.config.widgets.splice(3, 0, showAdvancedSearchLink, hideAdvancedSearchLink); }
Dynamic Form Extension!
Special Aikau Visibility Trick
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Set up some config for showing and hiding the advanced search features...
var showAdvancedSearch = { initialValue: false, rules: [ { topic: "ALF_SHOW_ADVANCED_SEARCH", attribute: "show", is: [true], isNot: [false] } ] }; var hideRegularSearch = { initialValue: true, rules: [ { topic: "ALF_SHOW_ADVANCED_SEARCH", attribute: "show", is: [false], isNot: [true] } ] };
What it Looks Like
What it Looks Like
Adding the Search Forms
Generating a New Search Interface
Generating a New Search Interface
Generating a New Search Interface
Generating a New Search Interface
Generating a New Search Interface
Page Creator Output
File: AcmeCSRForm.get.js
model.jsonModel = { publishOnReady: "", services: "", widgets: [ { name: "alfresco/forms/Form", config: { displayButtons: true, okButtonLabel: "OK", cancelButtonLabel: "Cancel", fieldId: "bff6e205-2af1-423b-81d7-79b2af7089a0", widgets: [ { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Id", name: "prop_acme_caseid", value: "", description: "Enter the case identifier" } },
Finding the Form to Append to
Finding the Search Code Hook
File: org/alfresco/share/pages/faceted-search/faceted-search.get.js
// Compose the search form model var searchForm = { id: "FCTSRCH_SEARCH_FORM", name: "alfresco/forms/SingleTextFieldForm", config: { useHash: true, okButtonLabel: msg.get("faceted-search.search-form.ok-button-label"), okButtonPublishTopic : "ALF_SET_SEARCH_TERM", okButtonPublishGlobal: true, okButtonIconClass: "alf-white-search-icon", okButtonClass: "call-to-action", textFieldName: "searchTerm", textBoxIconClass: "alf-search-icon", textBoxCssClasses: "long hiddenlabel", textBoxLabel: msg.get("faceted-search.search-form.search-field-label") } };
Grabbing the Form Hook
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
var searchForm = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_SEARCH_FORM"); if (searchForm && searchForm.config) { searchForm.config.scopeFormControls = false; searchForm.config.visibilityConfig = hideRegularSearch; }
Copy and Paste the Form Definition
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
var advancedSearchOptions = [ { label: “CSR View", value: JSON.stringify([ // paste form fields for form 1 here ]) }, { label: “Management View", value: JSON.stringify([ // paste form fields for form 2 here ]) } ];
After the Paste…
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Define two new custom forms - one for a CSR and one for a manager. Add as an array so that // we can use a selector to display and select which form: var advancedSearchOptions = [ { label: “CSR View", value: JSON.stringify([ // pasted form fields for form 1 here: { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Id", name: "prop_acme_caseid", value: "", description: "Enter the case identifier" } }, { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Customer", name: "prop_case_customer", value: "", description: "Enter the customer name" } }, { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Description", name: "prop_acme_casedescr", value: "", description: "Enter all or part of the case description" } }, { name: "alfresco/forms/controls/DojoSelect", config: { label: "Case Status", name: "prop_acme_casestatus", value: "", unitsLabel: "", description: "Select the case status", optionsConfig: { fixed: [ { label: "Pending Customer", value: "PendCustomer" }, { label: "Pending Support", }, { value: "PendSupport" }, label: "Pending Research", value: "PendReseach" { label: "Pending Engineering", value: "PendEngineering" }, { label: "Stale / Timed Out", value: "PendStale" } ] } } }, { name: "alfresco/forms/controls/DojoRadioButtons", config: { label: "Case Disposition", name: "prop_acme_casedisposition", value: "", description: "Select the case disposition", optionsConfig: { fixed: [ { label: "Case Open", value: "Open" }, { label: "Case Closed", value: "Closed" }, { label: "Case Deferred", value: "Deferred" } ] } } } ]) }, { label: “Management View", value: JSON.stringify([ // pasted form fields for form 2 here { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Id", name: "prop_acme_caseid", value: "", description: "Enter the case identifier" } }, { name: "alfresco/forms/controls/DojoValidationTextBox", config: { label: "Case Customer", name: "prop_case_customer", value: "", description: "Enter the customer name" } }, { name: "alfresco/forms/controls/DojoRadioButtons", config: { label: "Case Overdue", name: "prop_acme_caseoverdue", value: "", optionsConfig: { fixed: [ { label: "Yes", value: "true" }, { label: "No", value: "false" } ] } } } ]) } ];
Add the Container for the Forms
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Create a form for selecting the advanced form...
var advancedSearchFormSelect = { name: "alfresco/forms/Form", config: { showCancelButton: false, showOkButton: false, scopeFormControls: false, widgets: [ { name: "alfresco/forms/controls/DojoSelect", config: { fieldId: "ADVANCED_SEARCH_OPTION", label: "Acme Case Data Search", visibilityConfig: showAdvancedSearch, optionsConfig: { fixed: advancedSearchOptions } } } ] } };
Add the Container for the Forms
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Create a form for selecting the advanced form...
var advancedSearchFormSelect = { name: "alfresco/forms/Form", config: { showCancelButton: false, showOkButton: false, scopeFormControls: false, widgets: [ { name: "alfresco/forms/controls/DojoSelect", config: { fieldId: "ADVANCED_SEARCH_OPTION", label: "Acme Case Data Search", visibilityConfig: showAdvancedSearch, optionsConfig: { fixed: advancedSearchOptions } } } ] } };
Add the Code to Post the Form
File: alfresco/site-webscripts/com/acme/customizations/special-search/facted-search.get.js
// Create a dynamic form for displaying each advanced form...
var dynamicForm = { name: "alfresco/forms/DynamicForm", config: { visibilityConfig: showAdvancedSearch, scopeFormControls: false, subscriptionTopic: "_valueChangeOf_ADVANCED_SEARCH_OPTION", okButtonLabel: msg.get("faceted-search.search-form.ok-button-label"), okButtonPublishTopic: "ALF_ADVANCED_SEARCH", okButtonPublishGlobal: true, showCancelButton: false } }; // Bind the new widgets to the main stack...
var mainVerticalStack = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_MAIN_VERTICAL_STACK"); if (mainVerticalStack && mainVerticalStack.config && mainVerticalStack.config.widgets) { mainVerticalStack.config.widgets.splice(3, 0, advancedSearchFormSelect, dynamicForm); }
Finished Result Page
Finished Result Page
Project Complete!
Bonus: Adding a Custom Widget
It’s Easy and Fun!
Additional Scenario
• Add a Slider Widget to the Search Page • Allow a user to use a slide to search by file size • We will use a jQuery Slider, Since we like jQuery • We’ll need to set up the jQuery JavaScript and CSS libraries
Module Definition File
File: alfresco/site-data/extensions/slider-extension.xml
JavaScript Customization
File: alfresco/site-webscripts/com/alfresco/customizations/slider/facted-search.get.js
var searchForm = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_SEARCH_FORM"); if (searchForm) { searchForm.name = "alfresco/forms/Form"; searchForm.config = { okButtonLabel: "Custom Search", okButtonPublishTopic : "ALF_ADVANCED_SEARCH", okButtonPublishGlobal: true, widgets: [ { name: "summit/Slider", config: { name: "searchTerm", value: 50, label: "Slider", unitsLabel: "Gb" } } ] }; }
Finished Result Page
Q&A
Thank you for attending!
Finding the Search Code
File: org/alfresco/share/pages/faceted-search/faceted-search.get.js
// Put all components together var main = { id: "FCTSRCH_MAIN_VERTICAL_STACK", name: "alfresco/layout/VerticalWidgets", config: { baseClass: "side-margins", widgets: [ { name: "alfresco/html/Spacer", config: { height: "14px" } }, headingForSearchForm, searchForm, // more widget declarations
Finding the Search Code
File: org/alfresco/share/pages/faceted-search/faceted-search.get.js
main.config.widgets.splice(2, 0, scopeSelection); // Append services with those required for search services.push("alfresco/services/NavigationService", "alfresco/services/SearchService", "alfresco/services/ActionService", "alfresco/services/DocumentService", "alfresco/dialogs/AlfDialogService", "alfresco/services/PreferenceService", "alfresco/services/QuickShareService", "alfresco/services/RatingsService", "alfresco/services/CrudService", "alfresco/services/NotificationService", "alfresco/services/ContentService"); // Add in the search form and search doc lib...
widgets.unshift(accessMenu); widgets.push(main);