CSS Rules - What does this mean?

Hi,

I’m trying to work out how to style a type of button in a theme, and I’m getting tangled up in all sorts of sophisticated and confusing stuff!

I’m moderately css-aware, but this construct is confusing me :


.v-button.v-button-link,
.v-button.v-button-link:focus,
.v-button.v-button-link:active,
.v-button-link,
.v-button-link.v-pressed,
div > .v-button.v-button-link.v-disabled,
.v-ie7 .v-button-link.v-disabled {
	background: transparent;
	height: auto;
	padding: 0;
	cursor: pointer;
}

If there was a space inbetween .v-button and .v-button-link, we’d be selecting a component with a class of .vbutton-link if it is within a compoenent of class .v-button.

What does it mean if there is no space between the two? Anything?

Cheers,

Charles.

Hi Charles,

I’m the culprit of that horrible, though valid, CSS. I’m hoping to fix some of it in the next release, so if I can urge you to postpone your theming endeavor for two weeks, I think it would save us from a lot hair-loss.

But you we’re confused by the multiple classname selector…

So with a selector .foo.bar.classname you would be targeting all elements that have the class-attribute e.g. like this [code]

[/code] The order of the classnames is irrelevant, and there can be additional classnames as well. As long as all of the classnames specified in the selector are present in the elements class-attribute, that element will be selected.

Now, the .v-button.v-button-link selector will in effect select the same elements as .v-button-link (since both of the classnames are always present for link buttons), but what I’ve done is increasing the priority of the selector so it will not be overriden by more specific selectors. If I didn’t, a previously specified .v-button.v-button-disabled would override the link styles, which is unwanted.

Hope I helped to clarify some of it. And don’t hesitate to ask more about any CSS, I’m more than willing to give you pointers on any theme/CSS matters.

Ah, that makes sense; I’m trying to get a feel for how the theming & css works for the current themes, so I can apply customisations for my own theme for our apps. I’m looking forward to your - no doubt imminent :wink: - blog on the subject. I’ve been using the http://www.w3.org/TR/CSS2 specs as my css guidance, but any slightly more readable sites/references would be appreciated! I am using FireBug to analyze the resulting HTML/css - is this the best way to do this?

Anyway, what I am specifically trying to do at the moment is build a CustomComponent for paging - which will give me first, previous, next and last buttons (and probably some page numbers, a la Google) :

 public class PagingComponent extends CustomComponent implements Button.ClickListener, PropertyChangeListener {
  private Button bFirst = new Button("First", this);
  private Button bPrevious = new Button("Previous", this);
  private Button bNext = new Button("Next", this);
  private Button bLast = new Button("Last", this);

  public PagingComponent() {
    setStyleName("paging");
    initButton(bFirst, "first");
    initButton(bPrevious, "previous");
    initButton(bNext, "next");
    initButton(bLast, "last");

    HorizontalLayout mainLayout = new HorizontalLayout();
    mainLayout.addComponent(bFirst);
    mainLayout.addComponent(bPrevious);
    mainLayout.addComponent(bNext);
    mainLayout.addComponent(bLast);
...

  private void initButton(Button button, String iconPath) {
    button.setStyleName("link");
    button.setIcon(new ThemeResource("img/paging_" + iconPath + ".png"));
    button.setEnabled(false);
  }

OK - so I have now got a component with the requisite buttons, in the link style (so no borders to contend with) and with the icons coming from my theme. What I’d quite like to do now is “hide” the caption on the button, so all I get is the icon itself - and I’d rather not eliminate the caption from the code.

My current css is looking like this

 .paging .v-button .v-button-caption {
  display:none;
}

but the captions are still visible. I suspect (in fact, I’m pretty sure) that other, perhaps more specific, rules are overriding mine. How do I find out what they are?

Cheers,

Charles.

FWIW, the following will do what I want (I just cut-and-pasted what appeared to be the overriding rule from Firebug)

 div > .v-button.v-button-link .v-button-caption,
div > .v-button.v-button-link:focus .v-button-caption,
div > .v-button.v-button-link:active .v-button-caption,
.v-ie7 .v-button-link .v-button-caption,
.v-ie7 .v-button-link.v-pressed .v-button-caption,
div > .v-button.v-button-link.v-disabled .v-button-caption,
.v-ie7 .v-button-link.v-disabled .v-button-caption {	
	display: none;
}

But that will apply to all link buttons : how about restricting to just my “paging” component?
(My) Logic suggests that the follwing would be more specific :

 div.paging > .v-button.v-button-link .v-button-caption,
div.paging > .v-button.v-button-link:focus .v-button-caption,
div.paging > .v-button.v-button-link:active .v-button-caption,
.v-ie7 .paging .v-button-link .v-button-caption,
.v-ie7 .paging .v-button-link.v-pressed .v-button-caption,
div.paging > .v-button.v-button-link.v-disabled .v-button-caption,
.v-ie7 .paging .v-button-link.v-disabled .v-button-caption {	
	display: none;
}

But alas, no - with this, the captions reappear.

Cheers,

Charles

Ahh ! Got it ! Apologies for the stream of conciousness posts…


.paging div > .v-button.v-button-link .v-button-caption,
.paging div > .v-button.v-button-link:focus .v-button-caption,
.paging div > .v-button.v-button-link:active .v-button-caption,
.v-ie7 .paging  .v-button-link .v-button-caption,
.v-ie7 .paging .v-button-link.v-pressed .v-button-caption,
.paging div > .v-button.v-button-link.v-disabled .v-button-caption,
.v-ie7 .paging .v-button-link.v-disabled .v-button-caption {	
	display: none;
}

Any further pointers to useful CSS references and/or tools would still be appreciated!

Cheers,

Charles.

Great, you got it done.

So what was wrong with the earlier was the child selector “>”, which specified the selection to target only the direct child nodes of the div.paging (and since you have a HorizontalLayout in between the CustomComponent and the Buttons, the Buttons won’t be directly inside the CustomComponent’s div element).

I’ll try to come back with some a bit more lightweight reading than the CSS2.1 specs :slight_smile:

OK - I now have a fully working, model driven, re-usable paging component!

: First page
: All buttons enabled
: Last page

I’m happy to publish/share/contribute all of the code; in lieu of a better way to do it, I’ll embed it all here.
The only thing I can’t contribute are the icons themselves, as we’ve licensed them…

Hope this is of some use to someone - and, btw, this goes to show how quick it is to get up-and-running with Vaadin. As of Thursday last week, I had know nothing about it at all. Four working days later, I have written and used
a) A customized container seemlessly accessing a paged, hibernate-driven DAO (for table access without UI paging. Not without problems though, see other posts in Data Binding forum)
b) A standard outlook-layout-looky-likey main application (prototype)
c) This custom component for doing UI paging

No doubt there are some blunders in the code, but still : this goes to show that Vaadin is quick to get going with.

Cheers,

Charles


Usage Example

  PagingModel model = new PagingModel(10);
  PagingComponent pagingComponent = new PagingComponent(model);
  Label lCurrentPage = new Label("Current Page: ?");

  public void init() {
    setTheme("gemini");

    VerticalLayout layout = new VerticalLayout();
    layout.setSizeFull();
    Label spacer = new Label();
    layout.setSpacing(true);
    layout.setMargin(true);
    layout.addComponent(pagingComponent);
    layout.addComponent(lCurrentPage);
    layout.addComponent(spacer);
    layout.setExpandRatio(spacer, 1);

    updatePageNumber();

    setMainWindow(new Window("Paging Component", layout));

    model.addPropertyChangeListener("currentPage", new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        updatePageNumber();
      }
    });
  }

  private void updatePageNumber() {
    lCurrentPage.setValue("Current Page : " + model.getCurrentPage());
  }


Model


public class PagingModel {
  private int totalNumberOfPages;
  private int numPageNumbersToDisplay;
  private int currentPage = 1;
  private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

  public PagingModel(int totalNumberOfPages) {
    this(totalNumberOfPages, 5);
  }

  public PagingModel(int totalNumberOfPages, int numPageNumbersToDisplay) {
    this.totalNumberOfPages = totalNumberOfPages;
    this.numPageNumbersToDisplay = numPageNumbersToDisplay;
  }

  public int getCurrentPage() {
    return currentPage;
  }

  public void setCurrentPage(int currentPage) {
    int oldValue = this.currentPage;
    this.currentPage = currentPage;
    pcs.firePropertyChange("currentPage", oldValue, currentPage);
  }

  public int getTotalNumberOfPages() {
    return totalNumberOfPages;
  }

  public void setTotalNumberOfPages(int totalNumberOfPages) {
    int oldValue = this.totalNumberOfPages;
    this.totalNumberOfPages = totalNumberOfPages;
    pcs.firePropertyChange("totalNumberOfPages", oldValue, totalNumberOfPages);
  }

  public int getNumPageNumbersToDisplay() {
    return numPageNumbersToDisplay;
  }

  public void setNumPageNumbersToDisplay(int numPageNumbersToDisplay) {
    int oldValue = this.numPageNumbersToDisplay;
    this.numPageNumbersToDisplay = numPageNumbersToDisplay;
    pcs.firePropertyChange("rageSize", oldValue, numPageNumbersToDisplay);
  }

  public int getPreviousPage() {
    return currentPage - 1 < 1 ? 1 : currentPage - 1;
  }

  public int getNextPage() {
    return currentPage + 1 > totalNumberOfPages ? totalNumberOfPages : currentPage + 1;
  }

  public int getLastPageNumberToDisplay() {
    int rangeEnd = currentPage <= (numPageNumbersToDisplay / 2) ? numPageNumbersToDisplay
        : currentPage + (this.numPageNumbersToDisplay / 2);

    return rangeEnd > totalNumberOfPages ? totalNumberOfPages : rangeEnd;
  }

  public int getFirstPageNumbertoDisplay() {
    int rangeStart = getLastPageNumberToDisplay() - numPageNumbersToDisplay + 1;
    return rangeStart < 1 ? 1 : rangeStart;
  }

  public boolean isFirstPage() {
    return currentPage == 1;
  }

  public boolean hasNextPage() {
    return currentPage  < totalNumberOfPages;
  }

  public boolean hasPreviousPage() {
    return currentPage  > 1;
  }

  public boolean isLastPage() {
    return currentPage == totalNumberOfPages;
  }


  public void addPropertyChangeListener(PropertyChangeListener listener) {
    pcs.addPropertyChangeListener(listener);
  }

  public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    pcs.addPropertyChangeListener(propertyName, listener);
  }

  public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
    pcs.removePropertyChangeListener(propertyName, listener);
  }

  public void removePropertyChangeListener(PropertyChangeListener listener) {
    pcs.removePropertyChangeListener(listener);
  }

}


Component

 public class PagingComponent extends CustomComponent implements Button.ClickListener, PropertyChangeListener {
  private Button bFirst = new Button("First", this);
  private Button bPrevious = new Button("Previous", this);
  private Button bNext = new Button("Next", this);
  private Button bLast = new Button("Last", this);
  private HorizontalLayout pageNumbers = new HorizontalLayout();

  private PagingModel model;


  public PagingComponent(PagingModel model) {
    this();
    setModel(model);
  }

  public PagingComponent() {
    setStyleName("paging");
    initButton(bFirst, "first");
    initButton(bPrevious, "previous");
    initButton(bNext, "next");
    initButton(bLast, "last");

    HorizontalLayout mainLayout = new HorizontalLayout();
    mainLayout.addComponent(bFirst);
    mainLayout.addComponent(bPrevious);
    mainLayout.addComponent(pageNumbers);
    mainLayout.addComponent(bNext);
    mainLayout.addComponent(bLast);

    setCompositionRoot(mainLayout);
    configureButtons();
  }


  public PagingModel getModel() {
    return model;
  }

  public void setModel(PagingModel model) {
    PagingModel oldValue = model;
    this.model = model;

    if (oldValue != null) {
      oldValue.removePropertyChangeListener(this);
    }

    if (model != null) {
      model.addPropertyChangeListener(this);

    }

    configureButtons();
  }

  private void configureButtons() {
    if (model == null) {
      bFirst.setEnabled(false);
      bPrevious.setEnabled(false);
      bNext.setEnabled(false);
      bLast.setEnabled(false);
    } else {
      /* Enable/Disable the navigation buttons */
      bFirst.setEnabled(!model.isFirstPage());
      bPrevious.setEnabled(model.hasPreviousPage());
      bNext.setEnabled(model.hasNextPage());
      bLast.setEnabled(!model.isLastPage());

      /* Refresh the page numbers displayed */
      pageNumbers.removeAllComponents();
      for (int pageNumber = model.getFirstPageNumbertoDisplay(); pageNumber <= model.getLastPageNumberToDisplay(); pageNumber++) {
        Button pageButton = new Button(Integer.toString(pageNumber), new PageClickListener(pageNumber));
        pageButton.setStyleName("link");
        if (pageNumber == model.getCurrentPage()) {
          pageButton.addStyleName("current-page");
        }
        pageNumbers.addComponent(pageButton);
      }
    }
  }


  private void initButton(Button button, String iconPath) {
    button.setStyleName("link");
    button.addStyleName("navigation-button");
    button.setIcon(new ThemeResource("img/paging-" + iconPath + ".png"));
    button.setEnabled(false);
  }


  public void propertyChange(PropertyChangeEvent evt) {
    configureButtons();
  }

  public void buttonClick(Button.ClickEvent event) {
    Button clicked = event.getButton();
    if (clicked == bFirst) {
      model.setCurrentPage(1);
    } else if (clicked == bPrevious) {
      model.setCurrentPage(model.getCurrentPage() - 1);
    } else if (clicked == bNext) {
      model.setCurrentPage(model.getCurrentPage() + 1);
    } else if (clicked == bLast) {
      model.setCurrentPage(model.getTotalNumberOfPages());
    }
  }

  private class PageClickListener implements Button.ClickListener, Serializable {
    private final int page;

    public PageClickListener(int page) {this.page = page;}

    public void buttonClick(Button.ClickEvent event) {
      model.setCurrentPage(page);
    }
  }
}


CSS Styling



/* Hide all captions on navigation-buttons inside the paging compnent */
.paging div > .v-button.v-button-link.navigation-button .v-button-caption,
  .paging div > .v-button.v-button-link.navigation-button:focus .v-button-caption,
  .paging div > .v-button.v-button-link.navigation-button:active .v-button-caption,
  .v-ie7 .paging  .v-button-link.navigation-button .v-button-caption,
  .v-ie7 .paging .v-button-link.navigation-button.v-pressed .v-button-caption,
  .paging div > .v-button.v-button-link.navigation-button.v-disabled .v-button-caption,
  .v-ie7 .paging .v-button-link.navigation-button.v-disabled .v-button-caption {
  display: none;
}

/*  Highlight the current page button by making it bold */
.paging div > .v-button.v-button-link.current-page .v-button-caption,
  .paging div > .v-button.v-button-link.current-page:focus .v-button-caption,
  .paging div > .v-button.v-button-link.current-page:active .v-button-caption,
  .v-ie7 .paging  .v-button-link.current-page .v-button-caption,
  .v-ie7 .paging .v-button-link.current-page.v-pressed .v-button-caption,
  .paging div > .v-button.v-button-link.current-page.v-disabled .v-button-caption,
  .v-ie7 .paging .v-button-link.current-page.v-disabled .v-button-caption {

  font-weight: bold;

}

/* Make sure we have a margin on the captions of all link-buttons inside the paging component */
.paging div > .v-button.v-button-link .v-button-caption,
  .paging div > .v-button.v-button-link:focus .v-button-caption,
  .paging div > .v-button.v-button-link:active .v-button-caption,
  .v-ie7 .paging  .v-button-link .v-button-caption,
  .v-ie7 .paging .v-button-link.v-pressed .v-button-caption,
  .paging div > .v-button.v-button-link.v-disabled .v-button-caption,
  .v-ie7 .paging .v-button-link.v-disabled .v-button-caption {

  margin-left: 2px;
  margin-right: 2px;
}

/* Sort out the spacing, and stop the up-and-downing of the icons when pressed*/
.paging div > .v-button .v-icon,
  .v-ie7 .paging .v-button .v-icon,
  .paging div > .v-button.v-disabled .v-icon,
  .v-ie7 .paging .v-button.v-disabled .v-icon {
  margin: 1px;
}

Very nice one, great !

Actually, I was also thinking on making an UI paging control, as not all customers want un-pageable table in all situations and also, UI paging may also improve performance on browsers with the custom layouted columns.

Thank you for contributing Charles!

I think you should ask for commit access rights for Vaadin SVN
incubator
area. More info
here
.

WOW! Excellent! Please create a ticket for this and add these files there so that our R&D can pick it up and hopefully include it in some future version!
=> http://dev.vaadin.com/newticket/

…or even better. Commit it to the incubator AND create a ticket. :grin:

Hi All,

Joonas has contacted me as regards this component, and I will be committing this to the Incubator at some point in the near future.

Cheers,

Charles