On this blog, we've mostly discussed how to make stuff work, but Experience Builder is a web application and in web development making stuff pretty is often equally, some might say even more, important than functionality. If you want stuff to be pretty in Experience Builder, you should be using Layout Widgets. Seriously, you should be using Layout Widgets. I see your stuff from time to time and it is instantly clear whether or not you are using a Layout Widget. It's subtle, but it makes a huge difference in the overall professionalism of your site.
So let's run down what each of the Layout Widget does and maybe some sneaky ways you can use them that they weren't really meant for. I will cover a few widgets on this list that aren't strictly in the Layout Widget category, but serve an appearance focused function. I'll start with the Technically-Not-A-Layout-Widget Layout Widgets.
Not Actually Layout Widgets But They Totally Are Layout Widgets
Card Widget
Gives you a little box with a some pre-designed layout templates or clear out the pre-built design and make your own. The intended use is to make information boxes like these.
Alternative Use: The design options built into the Button Widget are fairly limited. If you want to design your own button, a Card is your best choice.
Divider Widget
There's really nothing to say here. It's a vertical or horizontal line. Do you need a vertical or horizontal line? Here, it's a vertical or horizontal line.
Seriously, many designs can be improved by visual section dividers and that's all this is. As for the styling options, stick to the single line. Just don't touch any of the other options.
I just want to hit them with a rolled up newspaper and say "Bad options. Bad, bad options."
Menu Widget
Makes a set of links to every Page in your project. Don't want a link to every Page in this Menu? Tough luck, you're linking to every Page whether you want to or not. If you want more control, you can use a number of other widgets (Button, Card, Text, Image, or List) that allow you to build a link instead.
Edit: See the correction by @JenniferAcunto in the comments. Thank you, but you ruined the set-up for the callback joke ahead.
Widget Controller Widget
Are your widgets getting out of control? Then, you need a Widget Controller Widget. It takes an ordinary widget and crams it down into a little button that will pop out to full size when you need it. Get to know all the options of the Widget Controller, they are all useful in some configuration or another.
Note for people using custom widgets: Widgets in a Widget Controller do not have their code read until they are first opened and don't stop if they are closed, unless that behavior was specifically programed in.
Fun Fact: If you put a Table Widget in a Widget Controller and then use the Add To Table Action, the Table will automatically open itself. I don't know if the developers meant to do that, but I hope they don't patch it out.
Section Widget
The Section Widget is like a mini-page inside your Page. These mini-pages are called Views. It allows you to put multiple things in one spot and swap out stuff as it becomes relevant to/useful to/desired by the user. It is super hackable and has made a starring role in these previous posts:
Makes a set of links to every View in a Section Widget. Don't want a link to every Section Widget in this Views Navigation? Tough luck... Wait...I'm being told that's an option now, but if you still want more options see the note about the Menu Widget above.
Officially Called Layout Widgets
Column Widget
A few weeks ago, I posted this screenshot.
Take a look at that grey bar under the Filter Widget and the widget itself appears to be slightly offset from the rest of the column. Sloppy, ugly, bad. Now, I put the stuff in that column into a Column Widget and give the Column a white background color.
Neater, acceptable, ok. I'd prefer to get rid of the border around the filter, at least that vertical border on the right, but it's a definite improvement.
A Column Widget neatly manages the spacing between widgets placed in a vertical arrangement. It also deals with widgets that change size.
Filter Widget collapsed, still looks fine.
Personal note: A few months ago, I was asked to stop using city data for ESRI Community stuff, so I started getting my data from an ESRI testing server I have bookmarked as Silly ESRI Data. Ok, that's not a great name for the bookmark as that server has data on everything on stuff around Jerry Seinfeld's apartment to natural disasters and homicide rates, but I'd much rather look at the more lighthearted end of the spectrum. So, that's why I have suddenly become obsessed with Bigfoot and UFOs.
Row Widget
A Column Widget for horizontal stuff. If you are making a Scrolling Page, all your stuff will be arranged into Row Widgets. Unless...
Fixed Panel Widget
The primary purpose of the Fixed Panel Widget is to break the row structure of a Scrolling Page Layout. You may want to use this if you have some information or tool that is taller than a Row(s) you are using for other widgets. You can also pin a Fixed Panel Widget to make a box that follows the user up and down the screen.
ArcGIS Dashboards is so much better than Experience Builder. Everything in Dashboards is on a grid and you can just lay it out by moving stuff around on the grid. Why does everything in Experience Builder have to be so complicated? Why can't I just use a grid? Oh, there is a grid. I can use a Grid Widget and lay stuff out on a grid. Why can't Dashboards be as flexible as Experience Builder, where I can lay stuff out on a grid or just stick stuff in random places?
(Be nice to ESRI developers. They do a lot. And ESRI customers are a highly demanding bunch that all think whatever they are doing is the most important thing in the world and that everyone cares about every tiny compliant they have as much as they do. We're kind of the worst.)
Historically, my favorite Template when I start a project has been Blank Fullscreen, but I think I'm going to start using Blank Fullscreen and covering it with a fullscreen Grid Widget. "Why not use the Blank Grid Template?," you ask. The Blank Grid Template does not give you the control over the spacing between widgets that the Grid Widget does. For most layouts, I am going to make my grid invisible by using a 0px gap or at least matching the colors, options I get from the Grid Widget, but not the Grid Template.
Pay attention to these cut box symbols as they allow you to split one box into two boxes, either horizontally or vertically. Or you can just drag in a new widget to whatever part of a grid box you like and the grid will split for you. By splitting and deleting boxes, you can build any arrangement possible. Put Grids inside your Grids, there's nothing stopping you.
Drop two or more widgets into the center of a Grid Widget to automatically turn a grid box into a set of tabs. Better than a Section and Views Navigation combo in many situations.
Look out for these hidden Distribute Space Vertically/Horizontally on the Grid Columns/Rows to quickly and neatly make the spacing even.
The Grid Widget also has these Allow Resize and Allow Expansion options that give the user control over the size of the widget within. With the Allow Expansion option being controllable on the box-by-box basis.
In short, Grids good.
Placeholder Widget
Do you see all those little plus signs everywhere in the Grid Widget section? Those are Placeholder Widgets. In the build mode, clicking that plus sign will allow you to place an new widget in that spot. In the real application, they have no appearance or functionality at all. Reminder: The Sidebar Problem™ is actually an every widget problem, including ones that have no appearance or function, like the Placeholder Widget.
There are two reasons you might want to use one of these things:
You aren't actually building an application. You are building a Template and you want your teammate to know where they should actually put the widgets.
You want to put some space between your other widgets, but you don't want anything visible in-between them. (An empty Text Widget would arguably do this job better.)
That's it. Those are all the Layout and Not-Exactly-Layout Widgets. Please use them. They exist for a reason.
The Didn't Exist When I First Wrote This Layout Widgets
Accordion Widget
In web design, an accordion refers to a collapsible panel or set of panels used to allow the user to open or close content as they need it. They look something like this.
Accordions serve a useful purpose, but they aren't well liked by design experts because users can miss something important hidden within them and they don't do well with screen-readers and other accessibility tools. The main rule of thumb is if your user needs to see it, it shouldn't be in an accordion.
Flow Row
An alternative version of a Row Widget. The other Row Widget is based on a 12 column layout that used to be a very common web standard. This 12 column system has largely been replaced by flexbox. Flexbox is a more intuitive, easier-to-use system for most purposes. Flow Row is flexbox based. You probably want to use this Row instead of that other Row.
1 Comment
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
';
}
}
}
catch(e){
}
}
}
if (newSub.getAttribute("slang").toLowerCase() != code_l.toLowerCase()) {
if (trLabelsHtml != "") {
var labelSname = "";
if(labelEle[i].querySelector("ul li:nth-child(1)").getAttribute("aria-hidden")){
labelSname = labelEle[i].querySelector("ul li:nth-child(1)").outerHTML;
}
labelEle[i].innerHTML = "";
labelEle[i].innerHTML = labelSname + trLabelsHtml;
}
}
}
}
}
catch(e){
}
}
}
/* V 2.0:3 = Store not translated reply id */
if(lingoRSXML.snapshotLength == 0){
if($scope.falseReplyID == "") {
$scope.falseReplyID = value;
}
}
/* Get translated Body of Replies/Comments */
var lingoRBXML = doc.evaluate(lingoRBExp, doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for(var i=0;i 0) {
var attachDiv = rootElement.querySelector('div.lia-quilt-row-main').querySelector('div.custom-attachments');
if (attachDiv) {
attachDiv = attachDiv.outerHTML;
}
else if(rootElement.querySelector('div.lia-quilt-row-main').querySelectorAll('#attachments').length > 0){
if ("BlogArticlePage" == "BlogArticlePage") {
attachDiv = rootElement.querySelector('div.lia-quilt-row-main .lia-message-body-content').querySelector('#attachments');
if (attachDiv) {
attachDiv = attachDiv.outerHTML;
}
else{
attachDiv = "";
}
}else{
attachDiv = rootElement.querySelector('div.lia-quilt-row-main').querySelector('#attachments').outerHTML;
}
}
else {
attachDiv = "";
}
/* Feedback Div */
var feedbackDiv = "";
var feedbackDivs = rootElement.querySelector('div.lia-quilt-row-main').querySelectorAll('div.lia-panel-feedback-banner-safe');
if (feedbackDivs.length > 0) {
for (var k = 0; k < feedbackDivs.length; k++) {
feedbackDiv = feedbackDiv + feedbackDivs[k].outerHTML;
}
}
}
else {
var attachDiv = rootElement.querySelector('div.lia-message-body-content').querySelector('div.Attachments.preview-attachments');
if (attachDiv) {
attachDiv = attachDiv.outerHTML;
} else {
attachDiv = "";
}
/* Everyone tags links */
if (document.querySelectorAll("div.TagList").length > 0){
var everyoneTagslink = document.querySelector('div.lia-quilt-row-main').querySelector(".MessageTagsTaplet .TagList");
if ((everyoneTagslink != null)||(everyoneTagslink != undefined)){
everyoneTagslink = everyoneTagslink.outerHTML;
}
else{
everyoneTagslink = "";
}
}
/* Feedback Div */
var feedbackDiv = "";
var feedbackDivs = rootElement.querySelector('div.lia-message-body-content').querySelectorAll('div.lia-panel-feedback-banner-safe');
if (feedbackDivs.length > 0) {
for (var m = 0; m < feedbackDivs.length; m++) {
feedbackDiv = feedbackDiv + feedbackDivs[m].outerHTML;
}
}
}
}
} catch (e) {
}
if (body_L == "") {
/* V 2.0:7 Replacing translated video data with source video data */
var newBodyVideoData = newBody.querySelectorAll('div[class*="video-embed"]');
angular.forEach($scope.videoData[value], function (sourceVideoElement, index) {
if (index <= (newBodyVideoData.length - 1)) {
newBodyVideoData[index].outerHTML = sourceVideoElement.outerHTML
}
});
/* V 2.0:7 = Replacing translated image data with source data */
var newBodyImageData = newBody.querySelectorAll('[class*="lia-image"]');
angular.forEach($scope.imageData[value], function (sourceImgElement, index) {
if (index <= (newBodyImageData.length - 1)) {
newBodyImageData[index].outerHTML = sourceImgElement.outerHTML;
}
});
/* V 2.0:7 = Replacing translated pre tag data with source data */
var newBodyPreTagData = newBody.querySelectorAll('pre');
angular.forEach($scope.preTagData[value], function (sourcePreTagElement, index) {
if (index <= (newBodyPreTagData.length - 1)) {
newBodyPreTagData[index].outerHTML = sourcePreTagElement.outerHTML;
}
});
}
var copyBodySubject = false;
if (body_L == "") {
copyBodySubject = true;
body_L = newBody.innerHTML;
}
/* This code is written as part of video fix by iTalent */
/* try{
var iframeHTMLText = body_L;
var searchIframeText = "<IFRAME";
var foundiFrameTag;
if (iframeHTMLText.indexOf(searchIframeText) > -1) {
foundiFrameTag = decodeHTMLEntities(iframeHTMLText);
foundiFrameTag = foundiFrameTag.split('src="')[1];
body_L = foundiFrameTag;
}
}
catch(e){
} */
/* This code is placed to remove the extra meta tag adding in the UI*/
try{
body_L = body_L.replace('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />','');
}
catch(e){
}
/** We should not replace the source content if user profile language and selected target language matches with source language **/
if(showTrContent) {
var compiled = false;
rootElement.querySelectorAll('div.lia-message-body-content')[0].innerHTML = null
if("BlogArticlePage"=="IdeaPage"){
// var customAttachDiv = '';
rootElement.querySelectorAll('div.lia-message-body-content')[0].innerHTML = body_L + feedbackDiv ;
$compile(rootElement.querySelectorAll('div.lia-message-body-content')[0])($scope);
compiled = true;
/* Attach atttach div */
// document.querySelector("div.translation-attachments-"+value).innerHTML = attachDiv;
rootElement.querySelectorAll('div.lia-message-body-content')[0].insertAdjacentHTML('afterend',attachDiv);
if(rootElement.querySelectorAll('div.lia-quilt-idea-message .lia-message-body .lia-attachments-message').length > 1){
rootElement.querySelectorAll('div.lia-quilt-idea-message .lia-message-body .lia-attachments-message')[1].remove();
}
} else {
if("BlogArticlePage"=="TkbArticlePage"){
rootElement.querySelectorAll('div.lia-message-body-content')[0].innerHTML = body_L + feedbackDiv ;
}else{
rootElement.querySelectorAll('div.lia-message-body-content')[0].innerHTML = body_L + feedbackDiv + attachDiv;
compiled = true;
}
}
/* Destroy and recreate OOyala player videos to restore the videos in target languages which is written by iTalent as part of iTrack LILICON-79 */ /* Destroy and recreate OOyala player videos */
try{
// $scope.videoData[value][0].querySelector("div").getAttribute("id");
for(var vidIndex=0; vidIndex<$scope.videoData[value].length; vidIndex++){
if( $scope.videoData[value][vidIndex].querySelector("div") != null){
var containerId = LITHIUM.OOYALA.players[$scope.videoData[value][vidIndex].querySelector("div").getAttribute("id")].containerId;
videoId = LITHIUM.OOYALA.players[$scope.videoData[value][vidIndex].querySelector("div").getAttribute("id")].videoId;
/** Get the Video object */
vid = OO.Player.create(containerId,videoId);
/** Destroy the video **/
vid.destroy();
/** recreate in the same position */
var vid = OO.Player.create(containerId,videoId);
}
}
}
catch(e){
}
try{
for(var vidIndex=0; vidIndex<($scope.videoData[value].length); vidIndex++){
if($scope.videoData[value][vidIndex].querySelector('video-js') != null){
var data_id = $scope.videoData[value][vidIndex].querySelector('video-js').getAttribute('data-video-id');
var data_account = $scope.videoData[value][vidIndex].querySelector('video-js').getAttribute('data-account');
var data_palyer = $scope.videoData[value][vidIndex].querySelector('video-js').getAttribute('data-player');
var div = document.createElement('div');
div.id = "brightcove";
div.class = "brightcove-player";
div.innerHTML =
'(view in my videos)'
var data = div.getElementsByClassName("video-js");
var script = document.createElement('script');
script.src = "https://players.brightcove.net/" + data_account + "/" + data_palyer + "_default/index.min.js";
for(var i=0;i< data.length;i++){
videodata.push(data[i]);
}
}
}
for(var i=0;i< videodata.length;i++){
document.getElementsByClassName('lia-vid-container')[i].innerHTML = videodata[i].outerHTML;
document.body.appendChild(script);
}
}
catch(e){
}
if(!compiled){
/* Re compile html */
$compile(rootElement.querySelectorAll('div.lia-message-body-content')[0])($scope);
}
}
if (code_l.toLowerCase() != newBody.getAttribute("slang").toLowerCase()) {
/* Adding Translation flag */
var tr_obj = $filter('filter')($scope.sourceLangList, function (obj_l) {
return obj_l.code.toLowerCase() === newBody.getAttribute("slang").toLowerCase()
});
if (tr_obj.length > 0) {
tr_text = "Esri may utilize third parties to translate your data and/or imagery to facilitate communication across different languages.".replace(/lilicon-trans-text/g, tr_obj[0].title);
try {
if ($scope.wootMessages[$rootScope.profLang] != undefined) {
tr_text = $scope.wootMessages[$rootScope.profLang].replace(/lilicon-trans-text/g, tr_obj[0].title);
}
} catch (e) {
}
} else {
//tr_text = "This message was translated for your convenience!";
tr_text = "Esri may utilize third parties to translate your data and/or imagery to facilitate communication across different languages.";
}
try {
if (!document.getElementById("tr-msz-" + value)) {
var tr_para = document.createElement("P");
tr_para.setAttribute("id", "tr-msz-" + value);
tr_para.setAttribute("class", "tr-msz");
tr_para.style.textAlign = 'justify';
var tr_fTag = document.createElement("IMG");
tr_fTag.setAttribute("class", "tFlag");
tr_fTag.setAttribute("src", "/html/assets/langTrFlag.PNG");
tr_fTag.style.marginRight = "5px";
tr_fTag.style.height = "14px";
tr_para.appendChild(tr_fTag);
var tr_textNode = document.createTextNode(tr_text);
tr_para.appendChild(tr_textNode);
/* Woot message only for multi source */
if(rootElement.querySelector(".lia-quilt-forum-message")){
rootElement.querySelector(".lia-quilt-forum-message").appendChild(tr_para);
} else if(rootElement.querySelector(".lia-message-view-blog-topic-message")) {
rootElement.querySelector(".lia-message-view-blog-topic-message").appendChild(tr_para);
} else if(rootElement.querySelector(".lia-quilt-blog-reply-message")){
rootElement.querySelector(".lia-quilt-blog-reply-message").appendChild(tr_para);
} else if(rootElement.querySelector(".lia-quilt-tkb-message")){
rootElement.querySelector(".lia-quilt-tkb-message").appendChild(tr_para);
} else if(rootElement.querySelector(".lia-quilt-tkb-reply-message")){
rootElement.querySelector(".lia-quilt-tkb-reply-message").insertBefore(tr_para,rootElement.querySelector(".lia-quilt-row.lia-quilt-row-footer"));
} else if(rootElement.querySelector(".lia-quilt-idea-message")){
rootElement.querySelector(".lia-quilt-idea-message").appendChild(tr_para);
} else if(rootElement.querySelector('.lia-quilt-occasion-message')){
rootElement.querySelector('.lia-quilt-occasion-message').appendChild(tr_para);
}
else {
if (rootElement.querySelectorAll('div.lia-quilt-row-footer').length > 0) {
rootElement.querySelectorAll('div.lia-quilt-row-footer')[0].appendChild(tr_para);
} else {
rootElement.querySelectorAll('div.lia-quilt-column-message-footer')[0].appendChild(tr_para);
}
}
}
} catch (e) {
}
}
} else {
/* Do not display button for same language */
// syncList.remove(value);
var index = $scope.syncList.indexOf(value);
if (index > -1) {
$scope.syncList.splice(index, 1);
}
}
}
}
});
});
/* V 1.1:2 = Reply Sync button for multi source translation */
} catch(e){
console.log(e);
}
};
if((rContent != undefined) && (rContent != "")) {
drawCanvas(decodeURIComponent(rContent));
/** Update variable with selected language code **/
$scope.previousSelCode = code_l;
}
};
/**
* @function manageTranslation
* @description Managess the translation of given language for the thread
* @param {string} langCode - Language Code
* @param {string} tid - Thread ID
*/
$scope.manageTranslation = function (langCode, tid) {
//debugger;
$scope.showTrText = false;
/* V 2.0:5 = actualStatus variable introduced to indicate detailed connector status on UI. This variable holds the actual translation percentage */
$scope.transPercent = "";
$scope.actualStatus = "";
if (tid != "") {
var bulkTranslation = lithiumPlugin.bulkTranslation(langCode, tid);
bulkTranslation.then(function (trContent) {
if(trContent.body != "") {
$scope.showPreview(trContent.body, $scope.mszList, langCode);
if(langCode != "en-US") {
$scope.showTrText = true;
}
}
if((trContent.status != "NA") && trContent.status != null) {
// $scope.transPercent = String(trContent.status);
$scope.actualStatus = String(trContent.status);
} else {
// $rootScope.errorMsg = "Translation is in progress. Please check again a few minutes."
$rootScope.errorMsg = "Translation is in progress. Please retry in a few minutes."
}
$scope.workbench = trContent.wb;
/* V 2.0:4 = Trigger uncalled or delayed callbacks (documnet uploaded/translation completed from lithium).*/
if(trContent.callback == 'true') {
var trCompletCallback = lithiumPlugin.trCompletCallback(langCode, trContent.docID);
trCompletCallback.then(function (callback){
// $rootScope.errorMsg = "Downloading Translated content in " + langCode + " now. Please check again in a few minutes."
$rootScope.errorMsg = "Uploading content to translate. Please check again in a few minutes."
});
} else if (trContent.callback == 'upload') {
var trCompletUpload = lithiumPlugin.trCompletUpload(langCode, trContent.docID);
trCompletUpload.then(function (callback) {
//$rootScope.errorMsg = "Uploading content to translate. Please check again in a few minutes."
$rootScope.errorMsg = "Uploading content to translate. Please check again in a few minutes."
});
} else if ("many" == "one") {
$scope.updateOOS();
} else if("SmartConx" == "SmartConx"){
if ("many" == "many"){
$scope.updateOOS();
}
}else if ((trContent.status != null) && trContent.status.includes("100")) {
/* If everything fine then only check Out of Sync status */
$scope.updateOOS();
} else {
/* If translation perccent is less than 100 then show the percentage on UI */
$scope.transPercent = $scope.actualStatus;
}
});
}
}
/**
* @function selectThisLang
* @description Called on select dropdown.
* @param {string} lang - Language code
*
*/
$scope.selectThisLang = function (lang, anonymousFlag) {
/* 1.4:3 Update Analytics on language selection */
try {
setTimeout(()=>{
lingoThreadLangSelected(lang, '1548209');
console.log("Language",lang);
},5000)
} catch (e) {
console.log(e);
}
/** Display Translated content **/
var getTranslation = lithiumPlugin.getTranslation(lang, "1548209");
getTranslation.then(function (trContent) {
if (trContent.body != "") {
$scope.showPreview(trContent.body, $scope.mszList, lang);
} else {
//$rootScope.errorMsg = "Translation is in progress. Please check again in a few minutes."
$rootScope.errorMsg = "Translation is in progress. Please retry in a few minutes."
}
});
};
var decodeEntities = (function() {
// this prevents any overhead from creating the object each time
var element = document.createElement('div');
function decodeHTMLEntities (str) {
if(str && typeof str === 'string') {
// strip script/html tags
str = str.replace(/