private void populateListing() { _listing.removeAllComponents(); getResourcesFromValue(getValue()) .forEach( r -> { Optional<ResourceRepositoryItem> repoItem = _repositoryDAO.getRepoItemForResource(r); if (repoItem.isPresent()) { ResourceRepositoryItemValueViewer viewer = new ResourceRepositoryItemValueViewer(repoItem.get()); PushButton remove = CommonActions.REMOVE.push(); AppUtil.enableTooltip(remove); remove.addActionListener( ev -> { RI val = removeResourceFromValue(repoItem.get().getResource(), getValue()); _repositoryDAO.mergeRepositoryItem(val); populateListing(); }); _listing.add(of("resource", of("actions persistence-actions", remove), viewer)); } }); }
@Override public void init() { super.init(); PushButton selectResources = new PushButton(createText(BUTTON_TEXT_SELECT_RESOURCES_FMT(), _terms.resources())); selectResources.addActionListener(ev -> beginResourceSelection()); selectResources.addClassName("select-resources"); populateListing(); if (_canEdit) { add(of("actions entity-actions", selectResources)); } add(_listing); }
@Override public void init() { // Make sure you call super.init() at the top of this method. /// See the Javadoc for #init() for more information about what it does. super.init(); // Set HTML element type and class names for presentation use on this Container component. setHTMLElement(HTMLElement.section); addClassName("user-profile-viewer"); // property_viewer is a standard class name. addClassName("property-viewer"); // Add microdata for programmatic / SEO use /// OR use RDFa support /// You typically only do this in viewers - not editors. setAttribute("itemscope", ""); setAttribute("itemtype", "http://schema.org/Person"); // setAttribute allows you to set any attribute as long as it will not interfere with a // component's /// native HTML. For example, you cannot set the "value" attribute on a Field since /// it uses that attribute. // It's a good idea to *not* mark variables final that you don't want in the scope of event // listeners. /// Hibernate/JPA entities are a great example of this pattern. You always need to re-attach /// entities before using them, so we should always call getUserProfile() in the context /// of handling an event. Note: our getUserProfile() method re-attaches the entity. UserProfile userProfile = getUserProfile(); Name name = userProfile.getName(); // You can use a Field for displaying non-internationalized content. /// It is desirable to do this since you don't need to create a LocalizedText. /// However, you cannot change the HTMLElement of a Field at this time, /// so some of the following code uses a Label which does allow /// specification of the HTMLElement. final Field namePrefix = new Field(name.getFormOfAddress(), false); final Field nameGiven = new Field(name.getFirst(), false); final Field nameFamily = new Field(name.getLast(), false); final Field nameSuffix = new Field(name.getSuffix(), false); // Sometimes it is easier and less error prone to make a component non-visible /// than checking for null on each use. Use this pattern with care. You don't /// want to consume a lot of resource unnecessarily. if (StringFactory.isEmptyString(namePrefix.getText())) namePrefix.setVisible(false); if (StringFactory.isEmptyString(nameSuffix.getText())) nameSuffix.setVisible(false); // Address Address address = userProfile.getPostalAddress(); // Address lines are always on their own line so we make sure they are enclosed by a block // element like a DIV.. final Label addressLine1 = new Label(); addressLine1.setHTMLElement(HTMLElement.div).addClassName("prop").addClassName("address_line"); final Label addressLine2 = new Label(); addressLine2.setHTMLElement(HTMLElement.div).addClassName("prop").addClassName("address_line"); if (address.getAddressLines().length > 0) addressLine1.setText(TextSources.create(address.getAddressLines()[0])); if (address.getAddressLines().length > 1) addressLine2.setText(TextSources.create(address.getAddressLines()[1])); final HTMLComponent city = new HTMLComponent(); // The "prop" class name is part of the standard HTML structure. It is always a good idea to // also /// add a specific class name like "city" in this example. Please be consistent when using class // names. /// For example, if everyone else is using "city", please use "city" too. Don't come up with // another class name /// that means something similar like "town" or "locality". Consistency has a big impact on /// the time required to style HTML as well as the ability to reuse CSS. city.setHTMLElement(HTMLElement.span).addClassName("prop").addClassName("city"); if (!StringFactory.isEmptyString(address.getCity())) { // Our microdata for the city shouldn't include the comma, so this is a bit more complicated // than the other examples. city.setText( TextSources.create( "<span itemprop=\"addressLocality\">" + address.getCity() + "</span><span class=\"delimiter\">,</span>")); } else city.setVisible(false); final Label state = new Label(TextSources.create(address.getState())); state.addClassName("prop").addClassName("state"); final Label postalCode = new Label(TextSources.create(address.getPostalCode())); postalCode.addClassName("prop").addClassName("postal_code"); // Other Contact final Field phoneNumber = new Field(userProfile.getPhoneNumber(), false); final Field emailAddress = new Field(userProfile.getEmailAddress(), false); // Social Contact final URILink twitterLink = userProfile.getTwitterLink() != null ? new URILink(_userProfileDAO.toURI(userProfile.getTwitterLink(), null)) : null; final URILink facebookLink = userProfile.getFacebookLink() != null ? new URILink(_userProfileDAO.toURI(userProfile.getFacebookLink(), null)) : null; final URILink linkedInLink = userProfile.getLinkedInLink() != null ? new URILink(_userProfileDAO.toURI(userProfile.getLinkedInLink(), null)) : null; // We are going to output HTML received from the outside, so we need to sanitize it first for // security reasons. /// Sometimes you'll do this sanitation prior to persisting the data. It depends on whether or // not you need to /// keep the original unsanitized HTML around. String processedHTML = userProfile.getAboutMeProse(); if (!StringFactory.isEmptyString(processedHTML)) { // Process the HTML converting links as necessary (adding JSESSIONID(s) /// for URL based session tracking, converting resource links to increase concurrent loading // limit, /// CMS link externalization, etc). /// This is *not* sanitation and should always be done before rendering - never before // persisting. /// We are doing this before sanitizing the HTML to avoid having to whitelist internal URL // protocols, etc. try { processedHTML = XMLRenderer.parseWithRoot(processedHTML, Event.getRequest(), Event.getResponse()); } catch (IOException e) { _logger.error("Unable to accept HTML: " + processedHTML, e); } // We don't trust the input, so we sanitize it with a whitelist of allowed HTML. Document dirty = Jsoup.parseBodyFragment(processedHTML, ""); Whitelist whitelist = Whitelist.relaxed(); // Don't allow users to use our website as a link farm whitelist.addEnforcedAttribute("a", "rel", "nofollow"); Cleaner cleaner = new Cleaner(whitelist); Document clean = cleaner.clean(dirty); processedHTML = clean.html(); } final HTMLComponent aboutMeProse = new HTMLComponent(processedHTML); Component aboutMeVideo = null; URL videoLink = userProfile.getAboutMeVideoLink(); if (videoLink != null) { // There are several ways to link to media (Youtube video URL, Vimeo video URL, Flickr URL, // internally hosted media file, etc). /// You can link to it. /// You can embed it. See http://oembed.com/ for a common protocol for doing this. /// If the link is to the media itself, you can create a player for it. /// Below is an example of creating a link to the video as well as a player. final URI videoLinkURI = _userProfileDAO.toURI(videoLink, null); URILink videoLinkComponent = new URILink(videoLinkURI, TextSources.create("My Video")); videoLinkComponent.setTarget("_blank"); IMediaUtility util = MediaUtilityFactory.getUtility(); try { // Check if we can parse the media and it has a stream we like. /// In our made up example, we're only accepting H.264 video. We don't care about the audio // in this example. IMediaMetaData mmd; if (util.isEnabled() && videoLinkURI != null && (mmd = util.getMetaData(videoLinkURI.toString())).getStreams().length > 0) { int width = 853, height = 480; // 480p default boolean hasVideo = false; for (IMediaStream stream : mmd.getStreams()) { if (stream.getCodec().getType() == ICodec.Type.video && "H264".equals(stream.getCodec().name())) { hasVideo = true; if (stream.getWidth() > 0) { width = stream.getWidth(); height = stream.getHeight(); } break; } } if (hasVideo) { Media component = new Media(); component.setMediaType(Media.MediaType.video); component.addSource(new MediaSource(videoLinkURI)); component.setFallbackContent(videoLinkComponent); component.setSize(new PixelMetric(width), new PixelMetric(height)); aboutMeVideo = component; } } } catch (IllegalArgumentException | RemoteException e) { _logger.error("Unable to get media information for " + videoLink, e); } if (aboutMeVideo == null) { // We could check for oEmbed support in case link was to youtube, vimeo, etc - // http://oembed.com/ // Since this is an example, we'll just output the link. aboutMeVideo = videoLinkComponent; } } ImageComponent picture = null; final FileEntity userProfilePicture = userProfile.getPicture(); if (userProfilePicture != null) { picture = new ImageComponent(new Image(userProfilePicture)); picture.setImageCaching( userProfilePicture .getLastModifiedTime() .before(new Date(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(60)))); } // Now that we've initialized most of the content, we'll add all the components to this View /// using the standard HTML structure for a property viewer. add( of( HTMLElement.section, "prop-group name", new Label(TextSources.create("Name")).setHTMLElement(HTMLElement.h1), namePrefix .setAttribute("itemprop", "honorificPrefix") .addClassName("prop") .addClassName("prefix"), nameGiven .setAttribute("itemprop", "givenName") .addClassName("prop") .addClassName("given"), nameFamily .setAttribute("itemprop", "familyName") .addClassName("prop") .addClassName("family"), nameSuffix .setAttribute("itemprop", "honorificSuffix") .addClassName("prop") .addClassName("suffix"))); // Add wrapping DIV to group address lines if necessary. Component streetAddress = (!StringFactory.isEmptyString(addressLine1.getText()) && !StringFactory.isEmptyString(addressLine2.getText()) ? of(HTMLElement.div, "address_lines", addressLine1, addressLine2) : (StringFactory.isEmptyString(addressLine1.getText()) ? addressLine2 : addressLine1) .setHTMLElement(HTMLElement.div)); streetAddress.setAttribute("itemprop", "streetAddress"); boolean hasAddress = (!StringFactory.isEmptyString(addressLine1.getText()) || !StringFactory.isEmptyString(addressLine2.getText()) || !StringFactory.isEmptyString(city.getText()) || !StringFactory.isEmptyString(state.getText()) || !StringFactory.isEmptyString(postalCode.getText())); boolean hasPhone = !StringFactory.isEmptyString(phoneNumber.getText()); boolean hasEmail = !StringFactory.isEmptyString(emailAddress.getText()); // We only want to output the enclosing HTML if we have content to display. if (hasAddress || hasPhone || hasEmail) { Container contactContainer = of( HTMLElement.section, "contact", new Label(TextSources.create("Contact Information")).setHTMLElement(HTMLElement.h1)); add(contactContainer); if (hasAddress) { contactContainer.add( of( HTMLElement.div, "prop-group address", // We are using an H2 here because are immediate ancestor is a DIV. If it was a // SECTION, /// then we would use an H1. See the UserProfileViewer for a comparison. new Label(TextSources.create("Address")).setHTMLElement(HTMLElement.h2), streetAddress, of( HTMLElement.div, "place", city, state.setAttribute("itemprop", "addressRegion"), postalCode.setAttribute("itemprop", "postalCode"))) .setAttribute("itemprop", "address") .setAttribute("itemscope", "") .setAttribute("itemtype", "http://schema.org/PostalAddress")); } if (hasPhone) { contactContainer.add( of( HTMLElement.div, "prop phone", new Label(TextSources.create("Phone")).setHTMLElement(HTMLElement.h2), phoneNumber.setAttribute("itemprop", "telephone"))); } if (hasEmail) { contactContainer.add( of( HTMLElement.div, "prop email", new Label(TextSources.create("Email")).setHTMLElement(HTMLElement.h2), emailAddress.setAttribute("itemprop", "email"))); } } if (twitterLink != null || facebookLink != null || linkedInLink != null) { Container social = of( HTMLElement.section, "social", new Label(TextSources.create("Social Media Links")).setHTMLElement(HTMLElement.h1)); add(social); if (twitterLink != null) { twitterLink.setTarget("_blank"); twitterLink.setText(TextSources.create("Twitter Link")); social.add(of(HTMLElement.div, "prop twitter", TextSources.create("Twitter"), twitterLink)); } if (facebookLink != null) { facebookLink.setTarget("_blank"); facebookLink.setText(TextSources.create("Facebook Link")); social.add( of(HTMLElement.div, "prop facebook", TextSources.create("Facebook"), facebookLink)); } if (linkedInLink != null) { linkedInLink.setTarget("_blank"); linkedInLink.setText(TextSources.create("LinkedIn Link")); social.add( of(HTMLElement.div, "prop linkedin", TextSources.create("LinkedIn"), linkedInLink)); } } final boolean hasAboutMeProse = StringFactory.isEmptyString(aboutMeProse.getText()); if (!hasAboutMeProse || aboutMeVideo != null) { Container aboutMe = of( HTMLElement.section, "about_me", new Label(TextSources.create("About Me")).setHTMLElement(HTMLElement.h1)); add(aboutMe); if (picture != null) { aboutMe.add(of(HTMLElement.div, "prop picture", TextSources.create("Picture"), picture)); } if (hasAboutMeProse) { aboutMe.add( of( HTMLElement.div, "prop prose", TextSources.create("Professional Information, Hobbies, Interests..."), aboutMeProse)); } if (aboutMeVideo != null) { Label label = new Label(TextSources.create("Video")).setHTMLElement(HTMLElement.label); label.addClassName("vl"); aboutMe.add(of(HTMLElement.div, "prop video", label, aboutMeVideo)); } } }