001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2017-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.webconlet.examples.helloworld; 020 021import freemarker.core.ParseException; 022import freemarker.template.MalformedTemplateNameException; 023import freemarker.template.Template; 024import freemarker.template.TemplateNotFoundException; 025import java.beans.ConstructorProperties; 026import java.io.IOException; 027import java.util.HashSet; 028import java.util.Optional; 029import java.util.Set; 030import org.jdrupes.json.JsonBeanDecoder; 031import org.jdrupes.json.JsonBeanEncoder; 032import org.jdrupes.json.JsonDecodeException; 033import org.jgrapes.core.Channel; 034import org.jgrapes.core.Event; 035import org.jgrapes.core.Manager; 036import org.jgrapes.core.annotation.Handler; 037import org.jgrapes.http.Session; 038import org.jgrapes.util.events.KeyValueStoreData; 039import org.jgrapes.util.events.KeyValueStoreQuery; 040import org.jgrapes.util.events.KeyValueStoreUpdate; 041import org.jgrapes.webconsole.base.Conlet.RenderMode; 042import org.jgrapes.webconsole.base.ConletBaseModel; 043import org.jgrapes.webconsole.base.ConsoleSession; 044import org.jgrapes.webconsole.base.UserPrincipal; 045import org.jgrapes.webconsole.base.WebConsoleUtils; 046import org.jgrapes.webconsole.base.events.AddConletType; 047import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource; 048import org.jgrapes.webconsole.base.events.ConletDeleted; 049import org.jgrapes.webconsole.base.events.ConsoleReady; 050import org.jgrapes.webconsole.base.events.DisplayNotification; 051import org.jgrapes.webconsole.base.events.NotifyConletModel; 052import org.jgrapes.webconsole.base.events.NotifyConletView; 053import org.jgrapes.webconsole.base.events.RenderConlet; 054import org.jgrapes.webconsole.base.events.RenderConletRequestBase; 055import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; 056 057/** 058 * Example of a simple conlet. 059 */ 060public class HelloWorldConlet 061 extends FreeMarkerConlet<HelloWorldConlet.HelloWorldModel> { 062 063 private static final Set<RenderMode> MODES = RenderMode.asSet( 064 RenderMode.Preview, RenderMode.View); 065 066 /** 067 * Creates a new component with its channel set to the given 068 * channel. 069 * 070 * @param componentChannel the channel that the component's 071 * handlers listen on by default and that 072 * {@link Manager#fire(Event, Channel...)} sends the event to 073 */ 074 public HelloWorldConlet(Channel componentChannel) { 075 super(componentChannel); 076 } 077 078 private String storagePath(Session session) { 079 return "/" + WebConsoleUtils.userFromSession(session) 080 .map(UserPrincipal::toString).orElse("") 081 + "/conlets/" + HelloWorldConlet.class.getName() + "/"; 082 } 083 084 /** 085 * Trigger loading of resources when the console is ready. 086 * 087 * @param event the event 088 * @param consoleSession the console session 089 * @throws TemplateNotFoundException the template not found exception 090 * @throws MalformedTemplateNameException the malformed template name exception 091 * @throws ParseException the parse exception 092 * @throws IOException Signals that an I/O exception has occurred. 093 */ 094 @Handler 095 public void onConsoleReady(ConsoleReady event, 096 ConsoleSession consoleSession) 097 throws TemplateNotFoundException, MalformedTemplateNameException, 098 ParseException, IOException { 099 // Add HelloWorldConlet resources to page 100 consoleSession.respond(new AddConletType(type()) 101 .addRenderMode(RenderMode.Preview).setDisplayNames( 102 localizations(consoleSession.supportedLocales(), "conletName")) 103 .addScript(new ScriptResource().setScriptUri( 104 event.renderSupport().conletResource(type(), 105 "HelloWorld-functions.js"))) 106 .addCss(event.renderSupport(), WebConsoleUtils.uriFromPath( 107 "HelloWorld-style.css"))); 108 KeyValueStoreQuery query = new KeyValueStoreQuery( 109 storagePath(consoleSession.browserSession()), consoleSession); 110 fire(query, consoleSession); 111 } 112 113 /** 114 * Invoked when the key/value store provides data. 115 * 116 * @param event the event 117 * @param channel the channel 118 * @throws JsonDecodeException the json decode exception 119 */ 120 @Handler 121 public void onKeyValueStoreData( 122 KeyValueStoreData event, ConsoleSession channel) 123 throws JsonDecodeException { 124 if (!event.event().query() 125 .equals(storagePath(channel.browserSession()))) { 126 return; 127 } 128 for (String json : event.data().values()) { 129 HelloWorldModel model = JsonBeanDecoder.create(json) 130 .readObject(HelloWorldModel.class); 131 putInSession(channel.browserSession(), model.getConletId(), model); 132 } 133 } 134 135 @Override 136 protected Optional<HelloWorldModel> createStateRepresentation( 137 RenderConletRequestBase<?> event, 138 ConsoleSession channel, String conletId) throws IOException { 139 HelloWorldModel conletModel = new HelloWorldModel(conletId); 140 String jsonState 141 = JsonBeanEncoder.create().writeObject(conletModel).toJson(); 142 channel.respond(new KeyValueStoreUpdate().update( 143 storagePath(channel.browserSession()) + conletModel.getConletId(), 144 jsonState)); 145 return Optional.of(conletModel); 146 } 147 148 @Override 149 protected Set<RenderMode> doRenderConlet(RenderConletRequestBase<?> event, 150 ConsoleSession channel, String conletId, 151 HelloWorldModel conletState) throws Exception { 152 Set<RenderMode> renderedAs = new HashSet<>(); 153 if (event.renderAs().contains(RenderMode.Preview)) { 154 Template tpl 155 = freemarkerConfig().getTemplate("HelloWorld-preview.ftl.html"); 156 channel.respond(new RenderConlet(type(), conletId, 157 processTemplate(event, tpl, 158 fmModel(event, channel, conletId, conletState))) 159 .setRenderAs( 160 RenderMode.Preview.addModifiers(event.renderAs())) 161 .setSupportedModes(MODES)); 162 renderedAs.add(RenderMode.Preview); 163 } 164 if (event.renderAs().contains(RenderMode.View)) { 165 Template tpl 166 = freemarkerConfig().getTemplate("HelloWorld-view.ftl.html"); 167 channel.respond(new RenderConlet(type(), conletState.getConletId(), 168 processTemplate(event, tpl, 169 fmModel(event, channel, conletId, conletState))) 170 .setRenderAs( 171 RenderMode.View.addModifiers(event.renderAs())) 172 .setSupportedModes(MODES)); 173 channel.respond(new NotifyConletView(type(), 174 conletState.getConletId(), "setWorldVisible", 175 conletState.isWorldVisible())); 176 renderedAs.add(RenderMode.View); 177 } 178 return renderedAs; 179 } 180 181 @Override 182 protected void doConletDeleted(ConletDeleted event, 183 ConsoleSession channel, String conletId, 184 HelloWorldModel conletState) throws Exception { 185 if (event.renderModes().isEmpty()) { 186 channel.respond(new KeyValueStoreUpdate().delete( 187 storagePath(channel.browserSession()) + conletId)); 188 } 189 } 190 191 @Override 192 protected void doUpdateConletState(NotifyConletModel event, 193 ConsoleSession channel, HelloWorldModel conletModel) 194 throws Exception { 195 event.stop(); 196 conletModel.setWorldVisible(!conletModel.isWorldVisible()); 197 198 String jsonState = JsonBeanEncoder.create() 199 .writeObject(conletModel).toJson(); 200 channel.respond(new KeyValueStoreUpdate().update( 201 storagePath(channel.browserSession()) + conletModel.getConletId(), 202 jsonState)); 203 channel.respond(new NotifyConletView(type(), 204 conletModel.getConletId(), "setWorldVisible", 205 conletModel.isWorldVisible())); 206 channel.respond(new DisplayNotification("<span>" 207 + resourceBundle(channel.locale()).getString("visibilityChange") 208 + "</span>") 209 .addOption("autoClose", 2000)); 210 } 211 212 /** 213 * Model with world's state. 214 */ 215 @SuppressWarnings("serial") 216 public static class HelloWorldModel extends ConletBaseModel { 217 218 private boolean worldVisible = true; 219 220 /** 221 * Creates a new model with the given type and id. 222 * 223 * @param conletId the web console component id 224 */ 225 @ConstructorProperties({ "conletId" }) 226 public HelloWorldModel(String conletId) { 227 super(conletId); 228 } 229 230 /** 231 * @param visible the visible to set 232 */ 233 public void setWorldVisible(boolean visible) { 234 this.worldVisible = visible; 235 } 236 237 public boolean isWorldVisible() { 238 return worldVisible; 239 } 240 } 241 242}