Started supporting resource creation by form

git-svn-id: http://svn.d4science-ii.research-infrastructures.eu/gcube/trunk/portlets/widgets/ckan-metadata-publisher-widget@129011 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Costantino Perciante 2016-06-07 15:50:33 +00:00
parent acd1343032
commit 60ecb288d6
20 changed files with 1127 additions and 61 deletions

View File

@ -156,9 +156,9 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.gcube.dataaccess</groupId> <groupId>org.gcube.data-catalogue</groupId>
<artifactId>ckan-util-library</artifactId> <artifactId>ckan-util-library</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>[1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.gcube.core</groupId> <groupId>org.gcube.core</groupId>

View File

@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@ -31,8 +32,17 @@ public interface CKanPublisherService extends RemoteService {
/** /**
* Try to create such dataset starting from the information contained into the toCreate bean. * Try to create such dataset starting from the information contained into the toCreate bean.
* @param toCreate * @param toCreate
* @return <b>true</b> on success, <b>false</b> otherwise * @return the identifier of the created dataset or null on error
*/ */
boolean createCKanDataset(DatasetMetadataBean toCreate); String createCKanDataset(DatasetMetadataBean toCreate);
/**
* Add this resource to the dataset whose id is datasetId
* @param resource
* @param datasetId
* @param owner of the dataset
* @param callback
*/
boolean addResourceToDataset(ResourceBean resource, String datasetId, String owner);
} }

View File

@ -2,6 +2,7 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
@ -33,6 +34,16 @@ public interface CKanPublisherServiceAsync {
* @return <b>true</b> on success, <b>false</b> otherwise * @return <b>true</b> on success, <b>false</b> otherwise
*/ */
void createCKanDataset(DatasetMetadataBean toCreate, void createCKanDataset(DatasetMetadataBean toCreate,
AsyncCallback<String> callback);
/**
* Add this resource to the dataset whose id is datasetId
* @param resource
* @param datasetId
* @param owner of the dataset
* @param callback
*/
void addResourceToDataset(ResourceBean resource, String datasetId, String owner,
AsyncCallback<Boolean> callback); AsyncCallback<Boolean> callback);
} }

View File

@ -0,0 +1,33 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.google.gwt.event.shared.GwtEvent;
/**
* Added resource event
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class AddResourceEvent extends GwtEvent<AddResourceEventHandler> {
public static Type<AddResourceEventHandler> TYPE = new Type<AddResourceEventHandler>();
private ResourceBean resource;
public AddResourceEvent(ResourceBean resource) {
this.resource = resource;
}
public ResourceBean getResource() {
return resource;
}
@Override
public Type<AddResourceEventHandler> getAssociatedType() {
return TYPE;
}
@Override
protected void dispatch(AddResourceEventHandler handler) {
handler.onAddedResource(this);
}
}

View File

@ -0,0 +1,11 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
import com.google.gwt.event.shared.EventHandler;
/**
* Added resource handler interface
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public interface AddResourceEventHandler extends EventHandler {
void onAddedResource(AddResourceEvent addResourceEvent);
}

View File

@ -3,7 +3,10 @@ import org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui.CustomFieldE
import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.GwtEvent;
/**
* Delete custom field event.
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class DeleteCustomFieldEvent extends GwtEvent<DeleteCustomFieldEventHandler> { public class DeleteCustomFieldEvent extends GwtEvent<DeleteCustomFieldEventHandler> {
public static Type<DeleteCustomFieldEventHandler> TYPE = new Type<DeleteCustomFieldEventHandler>(); public static Type<DeleteCustomFieldEventHandler> TYPE = new Type<DeleteCustomFieldEventHandler>();

View File

@ -2,6 +2,10 @@ package org.gcube.portlets.widgets.ckandatapublisherwidget.client.events;
import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.EventHandler;
/**
* Handler associated to the DeleteCustomFieldEvent
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public interface DeleteCustomFieldEventHandler extends EventHandler { public interface DeleteCustomFieldEventHandler extends EventHandler {
void onRemoveEntry(DeleteCustomFieldEvent event); void onRemoveEntry(DeleteCustomFieldEvent event);
} }

View File

@ -0,0 +1,142 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.github.gwtbootstrap.client.ui.AlertBlock;
import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.TextArea;
import com.github.gwtbootstrap.client.ui.TextBox;
import com.github.gwtbootstrap.client.ui.constants.AlertType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
/**
* Form used to add resource(s) to a dataset
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class AddResourceToDataset extends Composite{
private static AddResourceToDatasetUiBinder uiBinder = GWT
.create(AddResourceToDatasetUiBinder.class);
interface AddResourceToDatasetUiBinder extends
UiBinder<Widget, AddResourceToDataset> {
}
// bus to alert the dataset form about this new resource
private HandlerManager eventBus;
// the dataset id
private String datasetId;
// the owner
private String owner;
//Create a remote service proxy to talk to the server-side ckan service.
private final CKanPublisherServiceAsync ckanServices = GWT.create(CKanPublisherService.class);
@UiField TextBox resourceUrlTextBox;
@UiField TextBox resourceNameTextBox;
@UiField TextArea resourceDescriptionTextArea;
@UiField Button addResourceButton;
@UiField AlertBlock infoBlock;
public AddResourceToDataset(HandlerManager eventBus, String datasetId, String owner) {
initWidget(uiBinder.createAndBindUi(this));
// save bus
this.eventBus = eventBus;
// save dataset id (it is needed when we will add resources)
this.datasetId = datasetId;
// the owner of the dataset/files
this.owner = owner;
}
@UiHandler("addResourceButton")
void onAddButtonClick(ClickEvent e){
infoBlock.setVisible(false);
// validation
if(resourceUrlTextBox.getText().isEmpty()){
infoBlock.setType(AlertType.ERROR);
infoBlock.setText("URL cannot be empty");
infoBlock.setVisible(true);
return;
}
// remove html tags into description
//String description = convert(resourceDescriptionTextArea.getText()); TODO
// collect data and build up the bean
final ResourceBean resource = new ResourceBean(resourceUrlTextBox.getText(), resourceNameTextBox.getText(), resourceDescriptionTextArea.getText());
// disable add button
addResourceButton.setEnabled(false);
// try to create
ckanServices.addResourceToDataset(resource, datasetId, owner, new AsyncCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
if(result){
showAlert("Resource created correctly", AlertType.SUCCESS);
eventBus.fireEvent(new AddResourceEvent(resource));
}
else
showAlert("Unable to add this resource. Check the url is correct", AlertType.ERROR);
}
@Override
public void onFailure(Throwable caught) {
showAlert("Unable to add this resource, sorry", AlertType.ERROR);
}
});
}
/**
* Show error/success after resource creation attempt.
* @param text
* @param type
*/
protected void showAlert(String text, AlertType type) {
infoBlock.setText(text);
infoBlock.setType(type);
infoBlock.setVisible(true);
addResourceButton.setEnabled(true);
// hide after some seconds
Timer t = new Timer() {
@Override
public void run() {
infoBlock.setVisible(false);
}
};
t.schedule(4000);
}
}

View File

@ -0,0 +1,124 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:b="urn:import:com.github.gwtbootstrap.client.ui" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
.form-main-style {
margin-left: 10px;
}
.fieldset-border-style {
border: 1px groove #444;
-webkit-box-shadow: 0px 0px 0px 0px #000;
box-shadow: 0px 0px 0px 0px #000;
padding: 10px;
}
.legend-style {
width: auto;
padding: 10px;
margin-bottom: 0px;
}
@external .form-horizontal .input-large;
.form-horizontal .input-large {
width: 95%;
}
.block-alert-style {
margin-top: 10px;
padding: 10px;
margin-bottom: 10px;
}
.tagsPanelStyle {
display: inline-block;
}
.add-resource-button {
float: right;
}
.info-markdown {
width: 95%;
background-color: #ebebeb;
border-bottom: 1px thin;
border-left: 1px thin;
border-right: 1px thin;
padding: 3px;
}
</ui:style>
<g:HTMLPanel>
<b:Form type="HORIZONTAL" styleName="{style.form-main-style}"
ui:field="form">
<b:Fieldset styleName="{style.fieldset-border-style}">
<b:Legend styleName="{style.legend-style}">
Add Resource
<small>
<span style="color:red;">*</span>
is required
</small>
</b:Legend>
<b:ControlGroup>
<b:ControlLabel for="url" title="File url">
<font color="red">*</font>
URL:
</b:ControlLabel>
<b:Controls>
<b:TextBox alternateSize="LARGE" placeholder="http://example.com/image.jpg"
b:id="url" title="Dataset title" ui:field="resourceUrlTextBox" />
</b:Controls>
</b:ControlGroup>
<b:ControlGroup>
<b:ControlLabel for="name" title="Resource name">
Name:
</b:ControlLabel>
<b:Controls>
<b:TextBox alternateSize="LARGE" placeholder="my-image"
b:id="name" title="Resource name" ui:field="resourceNameTextBox" />
</b:Controls>
</b:ControlGroup>
<b:ControlGroup>
<b:ControlLabel for="description" title="Resource description">
Description:
</b:ControlLabel>
<b:Controls>
<b:TextArea alternateSize="LARGE"
placeholder="Some useful notes about data" b:id="description"
title="Resource description" ui:field="resourceDescriptionTextArea" />
<b:Paragraph styleName="{style.info-markdown}">
You can use
<b:Popover html="true"
text="__Bold text__ or _italic text_
# title
## secondary title
### etc
* list
* of
* items
http://auto.link.ed/
Please note: HTML tags are stripped out for security reasons"
heading="Markdown short guide">
<b:Button type="LINK">Markdown formatting</b:Button>
</b:Popover>
</b:Paragraph>
</b:Controls>
</b:ControlGroup>
<!-- Alert blocks for info/errors -->
<b:AlertBlock type="INFO" animation="true" visible="false"
ui:field="infoBlock" styleName="{style.block-alert-style}"></b:AlertBlock>
<!-- Add resource button -->
<b:Button title="Add resource" ui:field="addResourceButton" >Add</b:Button>
</b:Fieldset>
</b:Form>
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -0,0 +1,134 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.client.ui;
import java.util.ArrayList;
import java.util.List;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEvent;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.events.AddResourceEventHandler;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.constants.ButtonType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A summary of the added resources by the user.
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class AddedResourcesSummary extends Composite{
private static AddedResourcesSummaryUiBinder uiBinder = GWT
.create(AddedResourcesSummaryUiBinder.class);
interface AddedResourcesSummaryUiBinder extends
UiBinder<Widget, AddedResourcesSummary> {
}
// Event bus
private HandlerManager eventBus;
// list of added resources
List<ResourceBean> addedResources;
@UiField VerticalPanel addResourcesPanel;
public AddedResourcesSummary(HandlerManager eventBus) {
initWidget(uiBinder.createAndBindUi(this));
// save bus
this.eventBus = eventBus;
// bind on add resource event
bind();
// init list
addedResources = new ArrayList<ResourceBean>();
}
private void bind() {
// when a new resource is added
eventBus.addHandler(AddResourceEvent.TYPE, new AddResourceEventHandler() {
@Override
public void onAddedResource(AddResourceEvent addResourceEvent) {
// get the resource
final ResourceBean justAddedResource = addResourceEvent.getResource();
// check if a resource with this id already exists
for (ResourceBean resource : addedResources){
if(resource.getId().equals(justAddedResource.getId())){
// clear list and rebuild
rebuildSummary();
return;
}
}
Button associatedButton = new Button();
associatedButton.setType(ButtonType.LINK);
associatedButton.setText("-" + justAddedResource.getName());
// add to the list
addedResources.add(justAddedResource);
// add to the panel
addResourcesPanel.add(associatedButton);
// add handler to swap tab on click
associatedButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
// TODO show information below this link and swap panel
}
});
}
});
}
/**
* Rebuild the summary list
*/
protected void rebuildSummary() {
addResourcesPanel.clear();
for (final ResourceBean resource : addedResources){
Button associatedButton = new Button();
associatedButton.setType(ButtonType.LINK);
associatedButton.setText("-" + resource.getName());
// add to the list
addedResources.add(resource);
// add to the panel
addResourcesPanel.add(associatedButton);
// add handler to swap tab on click
associatedButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
// TODO show information below this link and swap panel
}
});
}
}
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
</ui:style>
<g:HTMLPanel>
<h2>Added Resources</h2>
<g:VerticalPanel ui:field="addResourcesPanel"></g:VerticalPanel>
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -7,7 +7,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.commons.lang.StringEscapeUtils;
import org.gcube.portlets.user.gcubewidgets.client.elements.Span; import org.gcube.portlets.user.gcubewidgets.client.elements.Span;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherServiceAsync;
@ -20,12 +19,14 @@ import com.github.gwtbootstrap.client.ui.AlertBlock;
import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.CheckBox; import com.github.gwtbootstrap.client.ui.CheckBox;
import com.github.gwtbootstrap.client.ui.ControlGroup; import com.github.gwtbootstrap.client.ui.ControlGroup;
import com.github.gwtbootstrap.client.ui.HelpBlock;
import com.github.gwtbootstrap.client.ui.ListBox; import com.github.gwtbootstrap.client.ui.ListBox;
import com.github.gwtbootstrap.client.ui.Tab;
import com.github.gwtbootstrap.client.ui.TabPanel;
import com.github.gwtbootstrap.client.ui.TextArea; import com.github.gwtbootstrap.client.ui.TextArea;
import com.github.gwtbootstrap.client.ui.TextBox; import com.github.gwtbootstrap.client.ui.TextBox;
import com.github.gwtbootstrap.client.ui.base.ListItem; import com.github.gwtbootstrap.client.ui.base.ListItem;
import com.github.gwtbootstrap.client.ui.constants.AlertType; import com.github.gwtbootstrap.client.ui.constants.AlertType;
import com.github.gwtbootstrap.client.ui.resources.Bootstrap.Tabs;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ClickHandler;
@ -39,6 +40,7 @@ import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
/** /**
@ -65,7 +67,6 @@ public class EditMetadataForm extends Composite{
@UiField FlowPanel tagsPanel; @UiField FlowPanel tagsPanel;
@UiField ListBox licenseListbox; @UiField ListBox licenseListbox;
@UiField ListBox visibilityListbox; @UiField ListBox visibilityListbox;
@UiField ListBox searchableListbox;
@UiField ListBox organizationsListbox; @UiField ListBox organizationsListbox;
@UiField TextBox versionTextbox; @UiField TextBox versionTextbox;
@UiField TextBox authorTextbox; @UiField TextBox authorTextbox;
@ -75,10 +76,19 @@ public class EditMetadataForm extends Composite{
@UiField ControlGroup customFields; @UiField ControlGroup customFields;
@UiField Button addCustomFieldButton; @UiField Button addCustomFieldButton;
@UiField CheckBox addResourcesCheckBox; @UiField CheckBox addResourcesCheckBox;
@UiField ControlGroup resourcesControlGroup;
@UiField Button createButton; @UiField Button createButton;
@UiField Button addResourcesButton;
@UiField Button resetButton; @UiField Button resetButton;
@UiField AlertBlock infoBlock; @UiField AlertBlock infoBlock;
@UiField AlertBlock onCreateAlertBlock; @UiField AlertBlock onCreateAlertBlock;
@UiField VerticalPanel resourcesPanel;
// tab panel
private TabPanel tabPanel;
// add resource form
AddResourceToDataset resourceForm;
// tags list // tags list
private List<String> tagsList = new ArrayList<String>(); private List<String> tagsList = new ArrayList<String>();
@ -95,14 +105,18 @@ public class EditMetadataForm extends Composite{
// dataset metadata bean // dataset metadata bean
private DatasetMetadataBean receivedBean; private DatasetMetadataBean receivedBean;
// the owner
private String owner;
/** /**
* Invoked in the most general case * Invoked in the most general case
* @param owner * @param owner
*/ */
public EditMetadataForm(String user) { public EditMetadataForm(String owner) {
initWidget(uiBinder.createAndBindUi(this)); initWidget(uiBinder.createAndBindUi(this));
this.owner = owner;
// bind on events // bind on events
bind(); bind();
@ -114,7 +128,7 @@ public class EditMetadataForm extends Composite{
resetButton.setEnabled(false); resetButton.setEnabled(false);
// get back the licenses and the metadata information // get back the licenses and the metadata information
ckanServices.getDatasetBean(null, user, new AsyncCallback<DatasetMetadataBean>() { ckanServices.getDatasetBean(null, owner, new AsyncCallback<DatasetMetadataBean>() {
@Override @Override
public void onSuccess(DatasetMetadataBean bean) { public void onSuccess(DatasetMetadataBean bean) {
@ -187,6 +201,9 @@ public class EditMetadataForm extends Composite{
} }
}); });
// hide the Add resources checkbox
resourcesControlGroup.setVisible(false);
} }
/** /**
@ -197,6 +214,8 @@ public class EditMetadataForm extends Composite{
public EditMetadataForm(String idFolderWorkspace, String owner) { public EditMetadataForm(String idFolderWorkspace, String owner) {
initWidget(uiBinder.createAndBindUi(this)); initWidget(uiBinder.createAndBindUi(this));
this.owner = owner;
// bind on events // bind on events
bind(); bind();
@ -355,7 +374,6 @@ public class EditMetadataForm extends Composite{
String description = descriptionTextarea.getText(); String description = descriptionTextarea.getText();
String selectedLicense = licenseListbox.getSelectedItemText(); String selectedLicense = licenseListbox.getSelectedItemText();
String visibility = visibilityListbox.getSelectedItemText(); String visibility = visibilityListbox.getSelectedItemText();
String searchable = searchableListbox.getSelectedItemText();
long version = Long.valueOf(versionTextbox.getValue()); long version = Long.valueOf(versionTextbox.getValue());
String author = authorTextbox.getValue(); String author = authorTextbox.getValue();
String authorEmail = authorEmailTextbox.getValue(); String authorEmail = authorEmailTextbox.getValue();
@ -374,7 +392,6 @@ public class EditMetadataForm extends Composite{
receivedBean.setVersion(version); receivedBean.setVersion(version);
receivedBean.setVisibility(visibility.equals("Public")); receivedBean.setVisibility(visibility.equals("Public"));
receivedBean.setTitle(title); receivedBean.setTitle(title);
receivedBean.setSearchable(searchable.equals("Yes"));
receivedBean.setTags(tagsList); receivedBean.setTags(tagsList);
receivedBean.setSelectedOrganization(chosenOrganization); receivedBean.setSelectedOrganization(chosenOrganization);
receivedBean.setAddResources(addResources); receivedBean.setAddResources(addResources);
@ -399,15 +416,62 @@ public class EditMetadataForm extends Composite{
onCreateAlertBlock.setText("Trying to create dataset, please wait"); onCreateAlertBlock.setText("Trying to create dataset, please wait");
onCreateAlertBlock.setVisible(true); onCreateAlertBlock.setVisible(true);
ckanServices.createCKanDataset(receivedBean, new AsyncCallback<Boolean>() { ckanServices.createCKanDataset(receivedBean, new AsyncCallback<String>() {
@Override @Override
public void onSuccess(Boolean result) { public void onSuccess(final String datasetId) {
if(result){ if(datasetId != null){
alertOnCreate("Dataset correctly created!", AlertType.SUCCESS); alertOnCreate("Dataset correctly created!", AlertType.SUCCESS);
// disable dataset fields
disableDatasetFields();
// if we are in the "general case" we need to show a form for adding resources
if(!resourcesControlGroup.isVisible()){
createButton.setVisible(false);
resetButton.setVisible(false);
// show the add resources button
addResourcesButton.setVisible(true);
addResourcesButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
// hide the button
addResourcesButton.setVisible(false);
// TabPanel
tabPanel = new TabPanel(Tabs.ABOVE);
tabPanel.setWidth("100%");
// add the form
resourceForm = new AddResourceToDataset(eventBus, datasetId, owner);
// tab for the form
Tab formContainer = new Tab();
formContainer.add(resourceForm);
formContainer.setHeading("Add New Resource");
tabPanel.add(formContainer);
// tab for the added resources
Tab addedResources = new Tab();
addedResources.add(new AddedResourcesSummary(eventBus));
addedResources.setHeading("Added Resource");
tabPanel.add(addedResources);
// add tabs to resources panel
tabPanel.selectTab(0);
resourcesPanel.add(tabPanel);
resourcesPanel.setVisible(true);
}
});
}
}else{ }else{
alertOnCreate("Unable to create this dataset, please retry later", AlertType.ERROR); alertOnCreate("Unable to create this dataset, please retry later", AlertType.ERROR);
@ -457,8 +521,8 @@ public class EditMetadataForm extends Composite{
* @return true on success * @return true on success
*/ */
private boolean validateData() { private boolean validateData() {
// TODO
return true; return true;
} }
@ -483,6 +547,24 @@ public class EditMetadataForm extends Composite{
} }
/**
* Disable dataset editable fields
*/
protected void disableDatasetFields() {
titleTextBox.setEnabled(false);
descriptionTextarea.setEnabled(false);
versionTextbox.setEnabled(false);
authorTextbox.setEnabled(false);
authorEmailTextbox.setEnabled(false);
maintainerTextbox.setEnabled(false);
maintainerEmailTextbox.setEnabled(false);
visibilityListbox.setEnabled(false);
tagsEnterTextBox.setEnabled(false);
licenseListbox.setEnabled(false);
organizationsListbox.setEnabled(false);
addCustomFieldButton.setEnabled(false);
}
/** /**
* change alert block behavior. * change alert block behavior.
* @param textToShow * @param textToShow

View File

@ -22,7 +22,6 @@
@external .form-horizontal .input-large; @external .form-horizontal .input-large;
.form-horizontal .input-large { .form-horizontal .input-large {
width: 95%; width: 95%;
margin-bottom: 15px;
} }
.block-alert-style { .block-alert-style {
@ -34,6 +33,15 @@
.tagsPanelStyle { .tagsPanelStyle {
display: inline-block; display: inline-block;
} }
.info-markdown {
width: 95%;
background-color: #ebebeb;
border-bottom: 1px thin;
border-left: 1px thin;
border-right: 1px thin;
padding: 3px;
}
</ui:style> </ui:style>
<g:HTMLPanel> <g:HTMLPanel>
<b:Form type="HORIZONTAL" styleName="{style.form-main-style}" <b:Form type="HORIZONTAL" styleName="{style.form-main-style}"
@ -83,6 +91,25 @@
<b:TextArea placeholder="eg. Some useful notes about the data" <b:TextArea placeholder="eg. Some useful notes about the data"
alternateSize="LARGE" b:id="description" title="Dataset description" alternateSize="LARGE" b:id="description" title="Dataset description"
ui:field="descriptionTextarea"></b:TextArea> ui:field="descriptionTextarea"></b:TextArea>
<b:Paragraph styleName="{style.info-markdown}">
You can use
<b:Popover html="true"
text="__Bold text__ or _italic text_
# title
## secondary title
### etc
* list
* of
* items
http://auto.link.ed/
Please note: HTML tags are stripped out for security reasons"
heading="Markdown formatting short guide">
<b:Button type="LINK">Markdown formatting</b:Button>
</b:Popover>
</b:Paragraph>
</b:Controls> </b:Controls>
</b:ControlGroup> </b:ControlGroup>
@ -135,23 +162,6 @@
</b:Controls> </b:Controls>
</b:ControlGroup> </b:ControlGroup>
<b:ControlGroup>
<b:ControlLabel for="searchable" title="Searchable">Searchable:</b:ControlLabel>
<b:Controls>
<b:ListBox b:id="searchable" title="Dataset searchable"
width="70%" ui:field="searchableListbox">
<g:item enabled="true" title="Yes">Yes</g:item>
<g:item title="No">No</g:item>
</b:ListBox>
<span style="float:right; width:256px;color: #aaaaaa;">
<b:Icon type="INFO_SIGN" size="TWO_TIMES" />
Searchable datasets can be searched by anyone, while
not-searchable datasets can only be accessed by entering directly
its URL.
</span>
</b:Controls>
</b:ControlGroup>
<b:ControlGroup> <b:ControlGroup>
<b:ControlLabel for="organization" title="Organizations">Publish in:</b:ControlLabel> <b:ControlLabel for="organization" title="Organizations">Publish in:</b:ControlLabel>
<b:Controls> <b:Controls>
@ -233,7 +243,7 @@
</b:Controls> </b:Controls>
</b:ControlGroup> </b:ControlGroup>
<b:ControlGroup> <b:ControlGroup ui:field="resourcesControlGroup">
<b:Controls> <b:Controls>
<b:CheckBox <b:CheckBox
title="Automatically add the folder content to the dataset as resource" title="Automatically add the folder content to the dataset as resource"
@ -251,7 +261,12 @@
<b:AlertBlock type="INFO" close="false" animation="true" <b:AlertBlock type="INFO" close="false" animation="true"
visible="false" ui:field="onCreateAlertBlock" styleName="{style.block-alert-style}"> visible="false" ui:field="onCreateAlertBlock" styleName="{style.block-alert-style}">
</b:AlertBlock> </b:AlertBlock>
<b:Button title="Add resources to the just created dataset" visible="false"
ui:field="addResourcesButton">Add Resources</b:Button>
<!-- Here will be placed the form for the resources -->
<g:VerticalPanel ui:field="resourcesPanel" visible="false" width="100%"></g:VerticalPanel>
<b:Button title="Create dataset" ui:field="createButton" <b:Button title="Create dataset" ui:field="createButton"
type="PRIMARY" block="true">Create</b:Button> type="PRIMARY" block="true">Create</b:Button>

View File

@ -1,5 +1,7 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.server; package org.gcube.portlets.widgets.ckandatapublisherwidget.server;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
@ -15,10 +17,11 @@ import org.gcube.common.homelibrary.home.workspace.WorkspaceItem;
import org.gcube.common.homelibrary.home.workspace.folder.FolderItem; import org.gcube.common.homelibrary.home.workspace.folder.FolderItem;
import org.gcube.common.homelibrary.home.workspace.folder.items.GCubeItem; import org.gcube.common.homelibrary.home.workspace.folder.items.GCubeItem;
import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.dataaccess.ckanutillibrary.CKanUtilsFactory; import org.gcube.datacatalogue.ckanutillibrary.CKanUtilsFactory;
import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetMetadataBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.LicensesBean;
import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceBean;
import org.gcube.vomanagement.usermanagement.UserManager; import org.gcube.vomanagement.usermanagement.UserManager;
import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeUser; import org.gcube.vomanagement.usermanagement.model.GCubeUser;
@ -53,7 +56,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
if(isWithinPortal()) if(isWithinPortal())
return ScopeProvider.instance.get(); return ScopeProvider.instance.get();
else else
return "/gcube"; return "/gcube"; // Development
} }
@ -67,7 +70,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
try{ try{
String currentScope = getCurrentScope(); String currentScope = getCurrentScope();
return "https://" + CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl(); return CKanUtilsFactory.getInstance().getCkanUtilsForScope(currentScope).getCatalogueUrl();
}catch(Exception e){ }catch(Exception e){
logger.error("Failed to retrieve catalogue url information", e); logger.error("Failed to retrieve catalogue url information", e);
} }
@ -92,7 +95,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
} }
return null; return null;
} }
/** /**
@ -300,7 +302,7 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
} }
@Override @Override
public boolean createCKanDataset(DatasetMetadataBean toCreate) { public String createCKanDataset(DatasetMetadataBean toCreate) {
// retrieve ckan's catalog url // retrieve ckan's catalog url
String ckanPortalUrl = getCatalogueUrl(); String ckanPortalUrl = getCatalogueUrl();
@ -334,10 +336,6 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
dataset.setMaintainerEmail(toCreate.getMaintainerEmail()); dataset.setMaintainerEmail(toCreate.getMaintainerEmail());
dataset.setVersion(String.valueOf(toCreate.getVersion())); dataset.setVersion(String.valueOf(toCreate.getVersion()));
dataset.setNotes(toCreate.getDescription()); dataset.setNotes(toCreate.getDescription());
logger.info("Searchable is " + toCreate.isSearchable() + " and visible is " + toCreate.getVisibility());
dataset.setPriv(false);
dataset.setOpen(toCreate.getVisibility()); dataset.setOpen(toCreate.getVisibility());
// iterate over the licenses to find the id of the chosen one // iterate over the licenses to find the id of the chosen one
@ -431,10 +429,10 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
logger.debug("Dataset created/updated " + res.getId()); logger.debug("Dataset created/updated " + res.getId());
else{ else{
logger.error("Dataset described by " + toCreate + " not created!"); logger.error("Dataset described by " + toCreate + " not created!");
return false; return null;
} }
return true; return res.getId();
} }
/** /**
@ -450,4 +448,66 @@ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements C
return convertedName; return convertedName;
} }
/**
* Utility methods
* @param URLName
* @return
*/
private static boolean exists(String URLName){
try {
HttpURLConnection.setFollowRedirects(true);
HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
con.setRequestMethod("HEAD");
logger.info("Return code is " + con.getResponseCode());
return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
}
catch (Exception e) {
logger.error("Exception while checking url", e);
return false;
}
}
@Override
public boolean addResourceToDataset(ResourceBean resourceBean, String datasetId, String owner) {
logger.info("Incoming request for creating new resource for dataset with id " + datasetId);
logger.info("Owner is " + owner + " and resource is " + resourceBean);
// of course, if it exists
if(exists(resourceBean.getUrl())){
try{
// retrieve ckan's catalog url
String ckanPortalUrl = getCatalogueUrl();
// retrieve the api key for this user
String apiKey = getCKANApikeyFromUser(owner);
CkanResource resource = new CkanResource(ckanPortalUrl, datasetId);
resource.setName(resourceBean.getName());
resource.setDescription(resourceBean.getDescription());
resource.setUrl(resourceBean.getUrl());
resource.setOwner(owner);
// Checked client
CheckedCkanClient client = new CheckedCkanClient(ckanPortalUrl, apiKey);
CkanResource createdRes = client.createResource(resource);
if(createdRes != null){
logger.info("Resource " + createdRes.getName() + " is now available");
return true;
}
}catch(Exception e){
logger.error("Unable to create new resource", e);
}
}
logger.info("No resource created");
return false;
}
} }

View File

@ -13,7 +13,6 @@ import java.util.Map;
* <li> Description -> folders' description * <li> Description -> folders' description
* <li> tags -> folder's custom fields keys' names * <li> tags -> folder's custom fields keys' names
* <li> visibility -> as chosen by the creator (visible = true, not visible = false) * <li> visibility -> as chosen by the creator (visible = true, not visible = false)
* <li> searchable -> as chosen by the creator
* <li> source -> url of the folder within the workspace * <li> source -> url of the folder within the workspace
* <li> version -> during creation it is going to be 1.0 * <li> version -> during creation it is going to be 1.0
* <li> author, maintainer -> folder's owner * <li> author, maintainer -> folder's owner
@ -34,7 +33,6 @@ public class DatasetMetadataBean implements Serializable {
List<String> tags; // on retrieve, they are the keys of the custom fields List<String> tags; // on retrieve, they are the keys of the custom fields
private String license; // chosen by the user private String license; // chosen by the user
private boolean visibility; // Private (false) or Public(true) private boolean visibility; // Private (false) or Public(true)
private boolean searchable; // true or false
private String source; // url of the folder in the workspace private String source; // url of the folder in the workspace
private long version; // version 1, 2 ... private long version; // version 1, 2 ...
private String author; // folder's owner fullname private String author; // folder's owner fullname
@ -46,7 +44,7 @@ public class DatasetMetadataBean implements Serializable {
private String selectedOrganization; private String selectedOrganization;
private List<String> resourcesIds; // in case of workspace, this is the list of children's ids private List<String> resourcesIds; // in case of workspace, this is the list of children's ids
private boolean addResources; // if true, add these files as resources to the dataset private boolean addResources; // if true, add these files as resources to the dataset
public DatasetMetadataBean(){ public DatasetMetadataBean(){
super(); super();
} }
@ -59,7 +57,6 @@ public class DatasetMetadataBean implements Serializable {
* @param tags * @param tags
* @param license * @param license
* @param visibility * @param visibility
* @param searchable
* @param source * @param source
* @param version * @param version
* @param author * @param author
@ -72,7 +69,7 @@ public class DatasetMetadataBean implements Serializable {
*/ */
public DatasetMetadataBean(String id, String title, String description, public DatasetMetadataBean(String id, String title, String description,
Map<String, String> customFields, List<String> tags, Map<String, String> customFields, List<String> tags,
String license, boolean visibility, boolean searchable, String license, boolean visibility,
String source, long version, String author, String authorEmail, String source, long version, String author, String authorEmail,
String maintainer, String maintainerEmail, String maintainer, String maintainerEmail,
String ownerFolderInWorkspace, List<String> organizationList, String ownerFolderInWorkspace, List<String> organizationList,
@ -85,7 +82,6 @@ public class DatasetMetadataBean implements Serializable {
this.tags = tags; this.tags = tags;
this.license = license; this.license = license;
this.visibility = visibility; this.visibility = visibility;
this.searchable = searchable;
this.source = source; this.source = source;
this.version = version; this.version = version;
this.author = author; this.author = author;
@ -161,14 +157,6 @@ public class DatasetMetadataBean implements Serializable {
this.visibility = visibility; this.visibility = visibility;
} }
public boolean isSearchable() {
return searchable;
}
public void setSearchable(boolean searchable) {
this.searchable = searchable;
}
public String getSource() { public String getSource() {
return source; return source;
} }
@ -254,7 +242,7 @@ public class DatasetMetadataBean implements Serializable {
return "DatasetMetadataBean [id=" + id + ", title=" + title return "DatasetMetadataBean [id=" + id + ", title=" + title
+ ", description=" + description + ", customFields=" + ", description=" + description + ", customFields="
+ customFields + ", tags=" + tags + ", license=" + license + customFields + ", tags=" + tags + ", license=" + license
+ ", visibility=" + visibility + ", searchable=" + searchable + ", visibility=" + visibility
+ ", source=" + source + ", version=" + version + ", author=" + ", source=" + source + ", version=" + version + ", author="
+ author + ", authorEmail=" + authorEmail + ", maintainer=" + author + ", authorEmail=" + authorEmail + ", maintainer="
+ maintainer + ", maintainerEmail=" + maintainerEmail + maintainer + ", maintainerEmail=" + maintainerEmail

View File

@ -0,0 +1,108 @@
package org.gcube.portlets.widgets.ckandatapublisherwidget.shared;
import java.io.Serializable;
/**
* A dataset's resource bean.
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
*/
public class ResourceBean implements Serializable{
private static final long serialVersionUID = -6542455246456049712L;
private String url;
private String name;
private String description;
private String id;
public ResourceBean(){
super();
}
/**
* @param url
* @param name
* @param description
*/
public ResourceBean(String url, String name, String description) {
super();
this.url = url;
this.name = name;
this.description = description;
}
/**
* @param url
* @param name
* @param description
* @param id
*/
public ResourceBean(String url, String name, String description, String id) {
super();
this.url = url;
this.name = name;
this.description = description;
this.id = id;
}
/**
* @return the url
*/
public String getUrl() {
return url;
}
/**
* @param url the url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the description
*/
public String getDescription() {
return description;
}
/**
* @param description the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "ResourceBean [url=" + url + ", name=" + name + ", description="
+ description + ", id=" + id + "]";
}
}

View File

@ -19,4 +19,7 @@
<source path='client' /> <source path='client' />
<source path='shared' /> <source path='shared' />
<!-- Specify the application specific style sheet. -->
<stylesheet src='CKanMetadataPublisher.css' />
</module> </module>

View File

@ -5,6 +5,8 @@
<link type="text/css" rel="stylesheet" href="CKanMetadataPublisher.css"> <link type="text/css" rel="stylesheet" href="CKanMetadataPublisher.css">
<script type="text/javascript" <script type="text/javascript"
src="CKanMetadataPublisher/CKanMetadataPublisher.nocache.js"></script> src="CKanMetadataPublisher/CKanMetadataPublisher.nocache.js"></script>
<script type="text/javascript"
src="/js/showdown.js"></script>
</head> </head>
<body> <body>

View File

@ -0,0 +1,325 @@
/**
* Created by Tivie on 06-01-2015.
*/
// Private properties
var showdown = {},
parsers = {},
extensions = {},
globalOptions = getDefaultOpts(true),
flavor = {
github: {
omitExtraWLInCodeBlocks: true,
prefixHeaderId: 'user-content-',
simplifiedAutoLink: true,
literalMidWordUnderscores: true,
strikethrough: true,
tables: true,
tablesHeaderId: true,
ghCodeBlocks: true,
tasklists: true
},
vanilla: getDefaultOpts(true)
};
/**
* helper namespace
* @type {{}}
*/
showdown.helper = {};
/**
* TODO LEGACY SUPPORT CODE
* @type {{}}
*/
showdown.extensions = {};
/**
* Set a global option
* @static
* @param {string} key
* @param {*} value
* @returns {showdown}
*/
showdown.setOption = function (key, value) {
'use strict';
globalOptions[key] = value;
return this;
};
/**
* Get a global option
* @static
* @param {string} key
* @returns {*}
*/
showdown.getOption = function (key) {
'use strict';
return globalOptions[key];
};
/**
* Get the global options
* @static
* @returns {{}}
*/
showdown.getOptions = function () {
'use strict';
return globalOptions;
};
/**
* Reset global options to the default values
* @static
*/
showdown.resetOptions = function () {
'use strict';
globalOptions = getDefaultOpts(true);
};
/**
* Set the flavor showdown should use as default
* @param {string} name
*/
showdown.setFlavor = function (name) {
'use strict';
if (flavor.hasOwnProperty(name)) {
var preset = flavor[name];
for (var option in preset) {
if (preset.hasOwnProperty(option)) {
globalOptions[option] = preset[option];
}
}
}
};
/**
* Get the default options
* @static
* @param {boolean} [simple=true]
* @returns {{}}
*/
showdown.getDefaultOptions = function (simple) {
'use strict';
return getDefaultOpts(simple);
};
/**
* Get or set a subParser
*
* subParser(name) - Get a registered subParser
* subParser(name, func) - Register a subParser
* @static
* @param {string} name
* @param {function} [func]
* @returns {*}
*/
showdown.subParser = function (name, func) {
'use strict';
if (showdown.helper.isString(name)) {
if (typeof func !== 'undefined') {
parsers[name] = func;
} else {
if (parsers.hasOwnProperty(name)) {
return parsers[name];
} else {
throw Error('SubParser named ' + name + ' not registered!');
}
}
}
};
/**
* Gets or registers an extension
* @static
* @param {string} name
* @param {object|function=} ext
* @returns {*}
*/
showdown.extension = function (name, ext) {
'use strict';
if (!showdown.helper.isString(name)) {
throw Error('Extension \'name\' must be a string');
}
name = showdown.helper.stdExtName(name);
// Getter
if (showdown.helper.isUndefined(ext)) {
if (!extensions.hasOwnProperty(name)) {
throw Error('Extension named ' + name + ' is not registered!');
}
return extensions[name];
// Setter
} else {
// Expand extension if it's wrapped in a function
if (typeof ext === 'function') {
ext = ext();
}
// Ensure extension is an array
if (!showdown.helper.isArray(ext)) {
ext = [ext];
}
var validExtension = validate(ext, name);
if (validExtension.valid) {
extensions[name] = ext;
} else {
throw Error(validExtension.error);
}
}
};
/**
* Gets all extensions registered
* @returns {{}}
*/
showdown.getAllExtensions = function () {
'use strict';
return extensions;
};
/**
* Remove an extension
* @param {string} name
*/
showdown.removeExtension = function (name) {
'use strict';
delete extensions[name];
};
/**
* Removes all extensions
*/
showdown.resetExtensions = function () {
'use strict';
extensions = {};
};
/**
* Validate extension
* @param {array} extension
* @param {string} name
* @returns {{valid: boolean, error: string}}
*/
function validate(extension, name) {
'use strict';
var errMsg = (name) ? 'Error in ' + name + ' extension->' : 'Error in unnamed extension',
ret = {
valid: true,
error: ''
};
if (!showdown.helper.isArray(extension)) {
extension = [extension];
}
for (var i = 0; i < extension.length; ++i) {
var baseMsg = errMsg + ' sub-extension ' + i + ': ',
ext = extension[i];
if (typeof ext !== 'object') {
ret.valid = false;
ret.error = baseMsg + 'must be an object, but ' + typeof ext + ' given';
return ret;
}
if (!showdown.helper.isString(ext.type)) {
ret.valid = false;
ret.error = baseMsg + 'property "type" must be a string, but ' + typeof ext.type + ' given';
return ret;
}
var type = ext.type = ext.type.toLowerCase();
// normalize extension type
if (type === 'language') {
type = ext.type = 'lang';
}
if (type === 'html') {
type = ext.type = 'output';
}
if (type !== 'lang' && type !== 'output' && type !== 'listener') {
ret.valid = false;
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
return ret;
}
if (type === 'listener') {
if (showdown.helper.isUndefined(ext.listeners)) {
ret.valid = false;
ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
return ret;
}
} else {
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
return ret;
}
}
if (ext.listeners) {
if (typeof ext.listeners !== 'object') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
return ret;
}
for (var ln in ext.listeners) {
if (ext.listeners.hasOwnProperty(ln)) {
if (typeof ext.listeners[ln] !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
' must be a function but ' + typeof ext.listeners[ln] + ' given';
return ret;
}
}
}
}
if (ext.filter) {
if (typeof ext.filter !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
return ret;
}
} else if (ext.regex) {
if (showdown.helper.isString(ext.regex)) {
ext.regex = new RegExp(ext.regex, 'g');
}
if (!ext.regex instanceof RegExp) {
ret.valid = false;
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
return ret;
}
if (showdown.helper.isUndefined(ext.replace)) {
ret.valid = false;
ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
return ret;
}
}
}
return ret;
}
/**
* Validate extension
* @param {object} ext
* @returns {boolean}
*/
showdown.validateExtension = function (ext) {
'use strict';
var validateExtension = validate(ext, null);
if (!validateExtension.valid) {
console.warn(validateExtension.error);
return false;
}
return true;
};