001/* 002 * #%L 003 * GwtMaterial 004 * %% 005 * Copyright (C) 2015 - 2017 GwtMaterialDesign 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package gwt.material.design.addins.client.masonry; 021 022import com.google.gwt.dom.client.Document; 023import com.google.gwt.event.shared.HandlerRegistration; 024import com.google.gwt.user.client.ui.IsWidget; 025import com.google.gwt.user.client.ui.Widget; 026import gwt.material.design.addins.client.MaterialAddins; 027import gwt.material.design.addins.client.base.constants.AddinsCssName; 028import gwt.material.design.addins.client.masonry.events.HasMasonryHandler; 029import gwt.material.design.addins.client.masonry.events.LayoutCompleteEvent; 030import gwt.material.design.addins.client.masonry.events.MasonryEvents; 031import gwt.material.design.addins.client.masonry.events.RemoveCompleteEvent; 032import gwt.material.design.addins.client.masonry.js.JsMasonry; 033import gwt.material.design.addins.client.masonry.js.JsMasonryOptions; 034import gwt.material.design.client.MaterialDesignBase; 035import gwt.material.design.client.base.HasDurationTransition; 036import gwt.material.design.client.base.JsLoader; 037import gwt.material.design.client.base.MaterialWidget; 038import gwt.material.design.client.constants.CssName; 039import gwt.material.design.client.ui.MaterialRow; 040 041import static gwt.material.design.addins.client.masonry.js.JsMasonry.$; 042 043//@formatter:off 044 045/** 046 * Masonry works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall. 047 * <p> 048 * <h3>XML Namespace Declaration</h3> 049 * <pre> 050 * {@code 051 * xmlns:m.addins='urn:import:gwt.material.design.addins.client.ui' 052 * } 053 * </pre> 054 * <p> 055 * <h3>UiBinder Usage:</h3> 056 * <pre> 057 * { 058 * @code 059 * <m.addins:MaterialMasonry> 060 * <m:MaterialColumn grid="l1" padding="4" backgroundColor="blue" height="200px"> 061 * <m:MaterialLabel text="1"/> 062 * </m:MaterialColumn> 063 * <-- Other columns here --> 064 * </m.addins:MaterialMasonry> 065 * } 066 * </pre> 067 * 068 * @author kevzlou7979 069 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#masonry">Material Masonry</a> 070 * @see <a href="https://github.com/desandro/masonry">Masonry 4.0.0</a> 071 */ 072//@formatter:on 073public class MaterialMasonry extends MaterialRow implements JsLoader, HasDurationTransition, HasMasonryHandler { 074 075 static { 076 if (MaterialAddins.isDebug()) { 077 MaterialDesignBase.injectDebugJs(MaterialMasonryDebugClientBundle.INSTANCE.masonryJsDebug()); 078 MaterialDesignBase.injectDebugJs(MaterialMasonryDebugClientBundle.INSTANCE.imageLoadedJsDebug()); 079 } else { 080 MaterialDesignBase.injectJs(MaterialMasonryClientBundle.INSTANCE.masonryJs()); 081 MaterialDesignBase.injectJs(MaterialMasonryClientBundle.INSTANCE.imageLoadedJs()); 082 } 083 } 084 085 private MaterialWidget sizerDiv = new MaterialWidget(Document.get().createDivElement()); 086 private JsMasonryOptions options = JsMasonryOptions.create(); 087 private JsMasonry masonryElement; 088 private Widget target; 089 090 public MaterialMasonry() { 091 super(Document.get().createDivElement(), AddinsCssName.MASONRY, CssName.ROW); 092 093 sizerDiv.setWidth("8.3333%"); 094 sizerDiv.setStyleName(AddinsCssName.COL_SIZER); 095 add(sizerDiv); 096 } 097 098 @Override 099 protected void onLoad() { 100 super.onLoad(); 101 102 load(); 103 104 masonryElement.on(MasonryEvents.REMOVE_COMPLETE, (e, param1) -> { 105 RemoveCompleteEvent.fire(this, target); 106 return true; 107 }); 108 109 masonryElement.on(MasonryEvents.LAYOUT_COMPLETE, (e, param1) -> { 110 LayoutCompleteEvent.fire(this); 111 return true; 112 }); 113 } 114 115 @Override 116 public void load() { 117 masonryElement = $(getElement()); 118 masonryElement.imagesLoaded(() -> masonryElement.masonry(options)); 119 } 120 121 @Override 122 protected void onUnload() { 123 super.onUnload(); 124 125 unload(); 126 } 127 128 @Override 129 public void unload() { 130 if (masonryElement != null) { 131 masonryElement.masonry("destroy"); 132 masonryElement.off(MasonryEvents.REMOVE_COMPLETE); 133 masonryElement.off(MasonryEvents.LAYOUT_COMPLETE); 134 } 135 } 136 137 @Override 138 public void reload() { 139 // reload items and layout specific to masonry plugin 140 if (masonryElement != null) { 141 layout(); 142 reloadItems(); 143 } 144 } 145 146 @Override 147 public boolean remove(Widget w) { 148 return remove((IsWidget) w); 149 } 150 151 @Override 152 public boolean remove(int index) { 153 remove(getWidget(index)); 154 return true; 155 } 156 157 @Override 158 public boolean remove(IsWidget child) { 159 Widget widget = (Widget) child; 160 masonryRemove(widget); 161 reload(); 162 return true; 163 } 164 165 /** 166 * Remove the item with Masonry support 167 */ 168 protected void masonryRemove(Widget target) { 169 this.target = target; 170 $(getElement()).masonry(options).masonry("remove", target.getElement()); 171 } 172 173 @Override 174 public void clear() { 175 for (Widget widget : getChildren()) { 176 remove(widget); 177 } 178 } 179 180 @Override 181 public void add(Widget child) { 182 super.add(child); 183 reload(); 184 } 185 186 @Override 187 protected void add(Widget child, com.google.gwt.user.client.Element container) { 188 super.add(child, container); 189 reload(); 190 } 191 192 @Override 193 protected void insert(Widget child, com.google.gwt.user.client.Element container, int beforeIndex, boolean domInsert) { 194 super.insert(child, container, beforeIndex, domInsert); 195 reload(); 196 } 197 198 @Override 199 public void insert(Widget child, int beforeIndex) { 200 super.insert(child, beforeIndex); 201 reload(); 202 } 203 204 /** 205 * Reload all items inside the masonry 206 */ 207 protected void reloadItems() { 208 masonryElement.masonry(options).masonry("reloadItems"); 209 } 210 211 /** 212 * Layout remaining item elements 213 */ 214 protected void layout() { 215 masonryElement.masonry(options).masonry("layout"); 216 } 217 218 /** 219 * Get the item selector. 220 */ 221 public String getItemSelector() { 222 return options.itemSelector; 223 } 224 225 /** 226 * Specifies which child elements will be used as item elements in the 227 * layout.It's .col by default for grid components. 228 */ 229 public void setItemSelector(String itemSelector) { 230 options.itemSelector = itemSelector; 231 } 232 233 /** 234 * Get the percent position boolean value. 235 */ 236 public boolean isPercentPosition() { 237 return options.percentPosition; 238 } 239 240 /** 241 * Sets item positions in percent values, rather than pixel values. percentPosition: true works 242 * well with percent-width items, as items will not transition their position on resize. 243 */ 244 public void setPercentPosition(boolean percentPosition) { 245 options.percentPosition = percentPosition; 246 } 247 248 /** 249 * Get the boolean value of origin left. 250 */ 251 public boolean isOriginLeft() { 252 return options.originLeft; 253 } 254 255 /** 256 * Controls the horizontal flow of the layout. By default, item elements start positioning at the 257 * left, with originLeft: true. Set originLeft: false for right-to-left layouts. 258 */ 259 public void setOriginLeft(boolean originLeft) { 260 options.originLeft = originLeft; 261 } 262 263 /** 264 * Get the boolean value of origin top. 265 */ 266 public boolean isOriginTop() { 267 return options.originTop; 268 } 269 270 /** 271 * Controls the vertical flow of the layout. By default, item elements start positioning at the top, 272 * with originTop: true. Set originTop: false for bottom-up layouts. It’s like Tetris 273 */ 274 public void setOriginTop(boolean originTop) { 275 options.originTop = originTop; 276 } 277 278 public MaterialWidget getSizerDiv() { 279 return sizerDiv; 280 } 281 282 @Override 283 public void setDuration(int duration) { 284 options.transitionDuration = duration + "ms"; 285 } 286 287 @Override 288 public int getDuration() { 289 return options.transitionDuration != null ? Integer.parseInt(options.transitionDuration.replace("ms", "")) : 0; 290 } 291 292 @Override 293 public HandlerRegistration addLayoutCompleteHandler(LayoutCompleteEvent.LayoutCompleteHandler handler) { 294 return addHandler(handler, LayoutCompleteEvent.TYPE); 295 } 296 297 @Override 298 public HandlerRegistration addRemoveCompleteHandler(RemoveCompleteEvent.RemoveCompleteHandler handler) { 299 return addHandler(handler, RemoveCompleteEvent.TYPE); 300 } 301}