package net.bootsfaces.render; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.component.UIForm; import net.bootsfaces.component.ajax.AJAXRenderer; import net.bootsfaces.component.form.Form; public class Responsive { private static String[] delimiters = new String[] {",", "..." }; private static String[] validValues = new String[] { "xs", "sm", "md", "lg", "tiny-screen", "small-screen", "medium-screen", "large-screen" }; public enum Sizes { xs, sm, md, lg } /** * Create the responsive class combination * @param r * @return */ public static String getResponsiveStyleClass(IResponsive r) { return getResponsiveStyleClass(r, true); } public static String getResponsiveStyleClass(IResponsive r, boolean forceColMd) { if(!shouldRenderResponsiveClasses(r)) { return ""; } int colxs = sizeToInt(getSize(r, Sizes.xs)); int colsm = sizeToInt(getSize(r, Sizes.sm)); int collg = sizeToInt(getSize(r, Sizes.lg)); int span = sizeToInt(r.getSpan()); int colmd = (span > 0) ? span : sizeToInt(getSize(r, Sizes.md)); if (colmd < 0) { if ((colxs > 0) || (colsm > 0) || (collg > 0)) { colmd = (colmd > 0) ? colmd : -1; } else { colmd = (colmd > 0) ? colmd : (forceColMd ? 12 : -1); } } int offs = sizeToInt(r.getOffset()); int offsmd = (offs > 0) ? offs : sizeToInt(r.getOffsetMd()); int oxs = sizeToInt(r.getOffsetXs()); int osm = sizeToInt(r.getOffsetSm()); int olg = sizeToInt(r.getOffsetLg()); StringBuilder sb = new StringBuilder(); if (colmd > 0 || offsmd > 0) { if (colmd > 0) { sb.append("col-md-").append(colmd); } if (offsmd > 0) { if (colmd > 0) { sb.append(" "); } sb.append("col-md-offset-" + offsmd); } } else if (colmd == 0) { if (forceColMd) { sb.append("col-md-12"); sb.append(" hidden-md"); } } if (colxs > 0) { sb.append(" col-xs-").append(colxs); } if (colxs == 0) { sb.append(" hidden-xs"); } if (colsm > 0) { sb.append(" col-sm-").append(colsm); } if (colsm == 0) { sb.append(" hidden-sm"); } if (collg > 0) { sb.append(" col-lg-").append(collg); } if (collg == 0) { sb.append(" hidden-lg"); } sb.append(encodeVisibility(r, r.getVisible(), "visible")); sb.append(encodeVisibility(r, r.getHidden(), "hidden")); if (oxs > 0) { sb.append(" col-xs-offset-").append(oxs); } if (osm > 0) { sb.append(" col-sm-offset-").append(osm); } if (olg > 0) { sb.append(" col-lg-offset-").append(olg); } return " " + sb.toString().trim() + " "; } /** * Encode the visible field * @param r * @return */ private static String encodeVisibility(IResponsive r, String value, String prefix) { if(value == null) return ""; List<String> str = wonderfulTokenizer(value, delimiters); return evaluateExpression(str, delimiters, validValues, prefix, r.getDisplay()); } /** * Decode col sizes between two way of definition * @param col * @param size * @return */ private static String getSize(IResponsiveLabel r, Sizes size) { String colSize = "-1"; switch(size) { case xs: colSize = r.getLabelColXs(); if(colSize.equals("-1")) colSize = r.getLabelTinyScreen(); break; case sm: colSize = r.getLabelColSm(); if(colSize.equals("-1")) colSize = r.getLabelSmallScreen(); break; case md: colSize = r.getLabelColMd(); if(colSize.equals("-1")) colSize = r.getLabelMediumScreen(); break; case lg: colSize = r.getLabelColLg(); if(colSize.equals("-1")) colSize = r.getLabelLargeScreen(); break; } return colSize; } private static String getSize(IResponsive r, Sizes size) { String colSize = "-1"; switch(size) { case xs: colSize = r.getColXs(); if(colSize.equals("-1")) colSize = r.getTinyScreen(); break; case sm: colSize = r.getColSm(); if(colSize.equals("-1")) colSize = r.getSmallScreen(); break; case md: colSize = r.getColMd(); if(colSize.equals("-1")) colSize = r.getMediumScreen(); break; case lg: colSize = r.getColLg(); if(colSize.equals("-1")) colSize = r.getLargeScreen(); break; } return colSize; } /** * Convert the specified size to int value * @param size * @return */ private static int sizeToInt(String size) { if (size==null) return -1; if ("full".equals(size)) return 12; if ("full-size".equals(size)) return 12; if ("fullSize".equals(size)) return 12; if ("full-width".equals(size)) return 12; if ("fullWidth".equals(size)) return 12; if ("half".equals(size)) return 6; if ("one-third".equals(size)) return 4; if ("oneThird".equals(size)) return 4; if ("two-thirds".equals(size)) return 8; if ("twoThirds".equals(size)) return 8; if ("one-fourth".equals(size)) return 3; if ("oneFourth".equals(size)) return 3; if ("three-fourths".equals(size)) return 9; if ("threeFourths".equals(size)) return 9; if (size.length()>2) { size=size.replace("columns", ""); size=size.replace("column", ""); size=size.trim(); } return new Integer(size).intValue(); } /** * Parse the expression token * * @param expressionToken * @param delimiters * @param validValues * @param visibilityLevel (visible or hidden) * @param display (block, inline or inline-block. default block) * @return */ public static String evaluateExpression( List<String> expressionToken, String[] delimiters, String[] validValues, String visibilityLevel, String display) { if ("visible".equals(visibilityLevel)) { display = (display == null || display.trim().isEmpty()) ? "" : "-" + display; } else { // hidden is not suffixed by -block, -inline or -inline-block display=""; } String finalExpr = ""; List<String> _valid = Arrays.asList(validValues); // Validate expression // // expression can be: // ONE SIZE: [size] // SIZE LIST: [size],[size],...,[size] // RANGE FROM: ...[size] or [size]... to get range (only inclusive) // RANGE BETWEEN: [size]...[size] // 1. Only one size: if(expressionToken.size() == 1) { finalExpr = " " + visibilityLevel + "-" + translateSize(expressionToken.get(0), true) + display; } // 2. Expression contains comma, so is a list of sizes else if (expressionToken.contains(",")) { for(String ex: expressionToken) { if(",".equals(ex)) continue; finalExpr += " " + visibilityLevel + "-" + translateSize(ex, true) + display + " "; } } // 3. Expression is a range from else if (expressionToken.size() == 2) { if(expressionToken.get(0).equals("...")) { if(!_valid.contains(expressionToken.get(1))) throw new FacesException("Expression not valid. Valid syntax is ...[size] eg. ...sm . Valid sizes are [ xs, sm, md, lg ]."); List<String> sR = getSizeRange("<=", translateSize(expressionToken.get(1), false)); for(String s: sR) { finalExpr += " " + visibilityLevel + "-" + s + display + " "; } } else if(expressionToken.get(1).equals("...")) { if(!_valid.contains(expressionToken.get(0))) throw new FacesException("Expression not valid. Valid syntax is [size]... eg. sm... . Valid sizes are [ xs, sm, md, lg ]."); List<String> sR = getSizeRange(">=", translateSize(expressionToken.get(0), false)); for(String s: sR) { finalExpr += " " + visibilityLevel + "-" + s + display + " "; } } else { throw new FacesException("Expression not valid. Valid syntax is ...[size] or [size]... . Valid sizes are [ xs, sm, md, lg ]."); } } // 4. Expression is in range else if(expressionToken.size() == 3) { // validation: if(!_valid.contains(expressionToken.get(0)) && !"...".equals(expressionToken.get(1)) && !_valid.contains(expressionToken.get(2))) throw new FacesException("Expression not valid. Valid syntax is [size]...[size] eg. xs...md . Valid sizes are [ xs, sm, md, lg ]."); List<String> sR2 = getSizeRange(expressionToken.get(1), translateSize(expressionToken.get(0), false), translateSize(expressionToken.get(2), false)); for(String s: sR2) { finalExpr += " " + visibilityLevel + "-" + s + display + " "; } } // 5. Otherwise else { throw new FacesException("Expression not valid. See the docs for a list of possibile rules."); } return finalExpr; } /** * Translate Sizes * @param size * @return */ public static String translateSize(String size, boolean strict) { if (strict) { boolean found=false; for (String s: validValues) { if (s.equals(size)) { found=true; break; } } if (!found) { throw new FacesException("The size of b:panel must be one of the values xs, sm, md, lg, tiny-screen, small-screen, medium-screen, or large-screen"); } } if(size.equalsIgnoreCase("xs") || size.equalsIgnoreCase("tiny-screen")) return "xs"; if(size.equalsIgnoreCase("sm") || size.equalsIgnoreCase("small-screen")) return "sm"; if(size.equalsIgnoreCase("md") || size.equalsIgnoreCase("medium-screen")) return "md"; if(size.equalsIgnoreCase("lg") || size.equalsIgnoreCase("large-screen")) return "lg"; return size; } /** * Get the size ranges * @param operation * @param size * @return */ private static List<String> getSizeRange(String operation, String size) { return getSizeRange(operation, size, null); } private static List<String> getSizeRange(String operation, String size1, String size2) { String[] orderSizes = { "xs", "sm", "md", "lg" }; List<String> sizeRange = new ArrayList<String>(); int itemIdx = Arrays.asList(orderSizes).indexOf(size1); if(operation.equals(">")) { for(int i = itemIdx + 1; i < orderSizes.length; i++) { sizeRange.add(orderSizes[i]); } } else if(operation.equals(">=")) { for(int i = itemIdx; i < orderSizes.length; i++) { sizeRange.add(orderSizes[i]); } } else if(operation.equals("<")) { for(int i = 0; i < itemIdx; i++) { sizeRange.add(orderSizes[i]); } } else if(operation.equals("<=")) { for(int i = 0; i <= itemIdx; i++) { sizeRange.add(orderSizes[i]); } } else if(operation.equals("...") && size2 != null) { int secondIdx = Arrays.asList(orderSizes).indexOf(size2); if(secondIdx < itemIdx) { int temp = secondIdx; secondIdx = itemIdx; itemIdx = temp; } for(int i = itemIdx; i <= secondIdx; i++) { sizeRange.add(orderSizes[i]); } } else { throw new FacesException("Operation not valid"); } return sizeRange; } /** * Tokenize string based on rules * * @param tokenString * @param delimiters * @return */ public static List<String> wonderfulTokenizer(String tokenString, String[] delimiters) { List<String> tokens = new ArrayList<String>(); String currentToken = ""; for(int i = 0; i < tokenString.length(); i++) { String _currItem = String.valueOf(tokenString.charAt(i)); if(_currItem.trim().isEmpty()) continue; String delimiterFound = ""; for(String d: delimiters) { if(d.startsWith(_currItem)) { if(d.length() > 1) { if(d.equals(tokenString.substring(i, i + d.length()))) { delimiterFound = d; i = i + (d.length() - 1); } } else delimiterFound = d; } if(!delimiterFound.isEmpty()) break; } if(!delimiterFound.isEmpty()) { if(!currentToken.isEmpty()) { tokens.add(currentToken); currentToken = ""; } tokens.add(delimiterFound); } else currentToken += _currItem; } if(!currentToken.isEmpty()) tokens.add(currentToken); return tokens; } /** * Create the responsive class combination * @param r the component bearing the responsiveness attributes * @return null, if there's no label-col-xx attribute */ public static String getResponsiveLabelClass(IResponsiveLabel r) { if(!shouldRenderResponsiveClasses(r)) { return ""; } int colxs = sizeToInt(getSize(r, Sizes.xs)); int colsm = sizeToInt(getSize(r, Sizes.sm)); int colmd = sizeToInt(getSize(r, Sizes.md)); int collg = sizeToInt(getSize(r, Sizes.lg)); StringBuilder sb = new StringBuilder(); if (colmd > 0) { sb.append("col-md-"); sb.append(colmd); sb.append(' '); } if (colxs > 0) { sb.append("col-xs-"); sb.append(colxs); sb.append(' '); } if (colsm > 0) { sb.append("col-sm-"); sb.append(colsm); sb.append(' '); } if (collg > 0) { sb.append("col-lg-"); sb.append(collg); sb.append(' '); } if (sb.length() > 0) { return sb.substring(0, sb.length() - 1); } return null; } /** * Temporal and ugly hack to prevent responsive classes to be applied to inputs inside inline forms. * * This should be removed and the logic placed somewhere else. * * @return whether the component should render responsive classes */ private static boolean shouldRenderResponsiveClasses(Object r) { // This method only checks inputs. if(r instanceof UIComponent && r instanceof IResponsiveLabel) { UIForm form = AJAXRenderer.getSurroundingForm((UIComponent) r, true); if(form instanceof Form) { if(((Form)form).isInline()) { // If the form is inline, no responsive classes should be applied return false; } } } return true; } // TEST METHOD public static void main(String[] args) { List<String> str = wonderfulTokenizer("sm...md", delimiters); String finalExpr = evaluateExpression(str, delimiters, validValues, "hidden", "block"); for(String s: str) System.out.println(s); System.out.println(finalExpr); System.out.println("*******"); str = wonderfulTokenizer("sm...medium-screen", delimiters); finalExpr = evaluateExpression(str, delimiters, validValues, "hidden", "block"); for(String s: str) System.out.println(s); System.out.println(finalExpr); System.out.println("*******"); str = wonderfulTokenizer("...md", delimiters); finalExpr = evaluateExpression(str, delimiters, validValues, "hidden", "block"); for(String s: str) System.out.println(s); System.out.println(finalExpr); System.out.println("*******"); str = wonderfulTokenizer("xs,lg", delimiters); finalExpr = evaluateExpression(str, delimiters, validValues, "hidden", "block"); for(String s: str) System.out.println(s); System.out.println(finalExpr); System.out.println("*******"); str = wonderfulTokenizer("md...", delimiters); finalExpr = evaluateExpression(str, delimiters, validValues, "hidden", "block"); for(String s: str) System.out.println(s); System.out.println(finalExpr); } }