Skip to end of metadata
Go to start of metadata

In my last two posts about Carbon LDP, I used a very simple Create Blog Postexample to show how to save a document and then how to save a document treeusing the JavaScript SDK. In this post, I build upon those examples by showing how to save a document using established vocabularies.

A Linked Data Platform best practices is to use re-use established linked data vocabularies instead of (re-)inventing duplicates. In my last two posts, we saw that you can create persist any arbitrary JavaScript object to Carbon, such as the one shown below.

content = {
	title: contentTitle,
	body: contentBody,
	publishedDate: pubDate
};

As a matter of convenience, Carbon will automatically generate vocabulary for the properties of such an object. That is to say, it will create a fully qualified URI for the property that is based on the application context you are working in. If the application istest-app/, for example, the following URIs are generated for the properties title, body, and publishedDate found in the JavaScript object shown above.

In most cases, however, you'll want to adhere to the LDP best practice by using terms from pre-existing, well-established vocabularies. The dcterms vocabulary from the Dublin Core®Metadata Initiative, for example. Since the Dublin Core terms are intended for describing media resources, it makes sense to use dcterms:title for the title of blog post content. Also, in order to make my data more flexible with different systems, I'd also like to store the title using the predicate (or property) type, rdfs:label. To do this, only minor modifications to the code we've been using are required.

We can add vocabulary for Carbon to use with the Carbon.extendObjectSchema method. Following is one way that works.

carbon.extendObjectSchema( {
    "dcterms": "http://purl.org/dc/terms/",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "title": {
        "@id": "dcterms:title",
        "@type": "xsd:string"
    },
    "label": {
        "@id": "rdfs:label",
        "@type": "xsd:string"
    }
} );

 

In the example above, we are defining the namespace prefixes and URIs of the vocabularies to use. Then we are telling the system to use specific terms within each vocabulary whenever a JavaScript object's property is found with the specified matching name. For example, anyObject.title would submit dcterms:title to the Carbon server. Likewise,anyObject.label would submit rdfs:label.

However, because we have specified no particular object types, these vocabulary terms will be used globally for any object found with matching properties, which we may not always want. For example, we may decide to use a #title term from one vocabulary for a given object type, and a #title term from some other vocabulary for another. Instead of defining the use of these terms globally, we can alternatively be more specific in the following way. We can define the vocabularies globally, but then specify the use of those vocabularies more specifically to a given object type as shown below.

// Define common prefixes in the general object schema
carbon.extendObjectSchema({
    "dcterms": "http://purl.org/dc/terms/",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "xsd": "http://www.w3.org/2001/XMLSchema#"
} );

// Use the specified terms only in the case of 
// BlogPost object type
carbon.extendObjectSchema( "http://example.org/ont/web#BlogPost", {	
    "title": {
        "@id": "dcterms:title",
        "@type": "xsd:string"
    },
    "label": {
        "@id": "rdfs:label",
        "@type": "xsd:string"
    }
} );

 

We also need to modify our JavaScript object to declare it has the type of BlogPost. This is done with the types property as shown below.

content = {
	title: contentTitle,
	label: contentTitle,
	body: contentBody,
	publishedDate: pubDate,
	types: ["http://example.org/ont/web#BlogPost"]
};

Complete Example

Here's all the code from the Create a BlogPost example exercise now.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="A chronological log of thoughts, personal reflections, discoveries, and random musings.">
	<meta name="author" content="Cody Burleson">
	<title>Create Blog Post</title>
	<link rel="icon" href="/images/favicon.ico">
	<link rel="author" href="https://plus.google.com/100082232329079808865"/>
	<link href="/css/bootstrap.min.css" rel="stylesheet"/>
	<link href="/css/styles.css" rel="stylesheet"/>
	<link id="link-rel-common" rel="import" href="/components/cb-common/common.html">
	<script src="/ckeditor/ckeditor.js"></script>
	<script src="/_head-script.js"></script>
	
</head>

<body>

	<article id="content">
		<div id="pageContainer" class="container">

			<div class="row">

				<div class="col-md-12">

					<h1>Create Blog Post</h1>

					<p class="lead">
						Share your thoughts with the world. 
					</p>
					
					<form>
						<div class="row">
							<div class="form-group col-xs-6">
								<label for="userName">Email (Login ID)</label> 
								<input type="text" class="form-control" id="userName" value="cody@base22.com">
							</div>
							<div class="form-group col-xs-6">
								<label for="password">Password</label>
								<!-- Change to type="password" in a real-world scenario... -->
								<input type="text" class="form-control" id="password" value="password">
							</div>
						</div>
						<div class="form-group">
							<label for="contentTitle">Title</label> 
							<input type="text" class="form-control" id="contentTitle" placeholder="Content Title">
						</div>
						<div class="form-group">
						    <label for="editor1">Body</label>
						    <textarea class="form-control" id="editor1" name="editor1" rows="10" cols="80">
						     Write your brilliant words here.
						    </textarea>
						</div>
						<div class="form-group">
							<label for="slug">Slug</label> 
							<input type="text" class="form-control" id="contentSlug" placeholder="content-uri-slug">
						</div>

			            <script>
			                // Replace the <textarea id="editor1"> with a CKEditor
			                // instance, using default configuration.
			                CKEDITOR.replace( 'editor1' );
			            </script>
			            <p></p>
			            <div class="alert alert-success" role="alert" id="alertSuccess" style="display:none">
						  <p><strong>Document saved successfully!</strong><br/></p>
						  <a href="#" class="alert-link" id="alertSuccessDocId"></a>
						</div>
			            
			            <button type="button" class="btn btn-primary" id="submitButton">Submit</button>
			        </form>

				</div>

				<!-- ***** RIGHT PANEL ***** -->

				<div id="right" class="col-md-4">


				</div><!-- /.col-md-4 -->

			</div><!-- /.row -->
		</div><!-- /.container -->
	</article>

	<script src="/javascript/all.js"></script>
	<script src="/_footer-script.js"></script>
	<!-- Source: ./src/main/typescript/create-content.ts -->
	
	
	<script src="/javascript/moment.min.js"></script>
	
	<!-- IE required polyfill -->
	<!-- Copied by gulp copy-resources from node_modules/core-js/client/shim.js -->
	<script src="/javascript/shim.min.js"></script>
	
	<script src="/javascript/Carbon.sfx.js"></script>
	
	<script>
	
	(function() {

		var APP_SLUG = 'test-app/';
		
		var carbon = new Carbon();
		carbon.setSetting( "domain", "localhost:8083" );
		carbon.setSetting( "http.ssl", false );
		
		// Define common prefixes in the general object schema
		carbon.extendObjectSchema({
		    "dcterms": "http://purl.org/dc/terms/",
		    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
		    "xsd": "http://www.w3.org/2001/XMLSchema#"
		} );
		
		// Use the specified terms only in the case of 
		// BlogPost object type
		carbon.extendObjectSchema( "http://example.org/ont/web#BlogPost", {	
		    "title": {
		        "@id": "dcterms:title",
		        "@type": "xsd:string"
		    },
		    "label": {
		        "@id": "rdfs:label",
		        "@type": "xsd:string"
		    }
		} );
		
		// Returns the slug (document identifying portion) of the given URI.
		function getSlug(uri) {
			// Snip off the trailing slash, if the uri has one...
			if(uri.endsWith('/')) {
				uri = uri.substring(0,uri.length-1);
			}
			// Return all characters after the last remaining slash if one 
			// exists, otherwise return the given string
			if ( uri.indexOf('/') != -1 ) {
				var lastSlashNdx = uri.lastIndexOf('/');
				return uri.substring(lastSlashNdx + 1, uri.length);
			} else {
				return uri;
			}
		}

		function getOrCreateAndGetChildDocument(myAppContext,parentDocId,docId,doc) {
			console.log(">> getOrCreateAndGetDocument()");
			var slug = getSlug(docId);
			return myAppContext.documents.exists( docId ).then(
	    		function( promiseResult ){
					if( promiseResult[0] ) {
						console.log("Document exists; fetching it...");
						return myAppContext.documents.get( docId );
					} else {
						console.log("Document does not exist; creating it...");
						return myAppContext.documents.createChildAndRetrieve( parentDocId, doc, slug );
					}
	    		}
	    	);
		}
		
		
		function submitContent() {
			
			// Get all the values from the UI Form...
			// (No form validation going on here yet)
		    var contentTitle = document.getElementById("contentTitle").value;
		    var contentBody = CKEDITOR.instances.editor1.getData();
		    
		    var pubDate = moment().format();
		    var pubYear = moment().format('YYYY');
		    var pubMonth = moment().format('MM');
		    var pubDay = moment().format('DD');
		    
		    var contentSlug = document.getElementById("contentSlug").value;
		    var userName = document.getElementById("userName").value;
		    var password = document.getElementById("password").value;

		    var myAppContext,
		    	content,
		    	blogContainer,
		    	yearContainer,
		    	monthContainer,
		    	dayContainer;
		    
		    return carbon.auth.authenticate( userName, password ).then(
			    	function( token ) {
			    		//console.log( "Authenticated Agent: %o",carbon.auth.authenticatedAgent ); // Yourself!
			    		return carbon.apps.getContext( APP_SLUG );
			    	}
			    ).then(
			    	function( appContext ) {
			    		myAppContext = appContext; // retrieving your app context
			    		
			    		// First, lets create the blog container
			    		
			    		// ID of the Carbon document you want to retrieve
						//var containerId = "http://localhost:8083/apps/" + APP_SLUG + "blog2/";
			    		var containerId = "blog/";
						
			    		blogContainer = {
			    			title: "Blog"
			    		};
			    		
			    		// Create the Blog container if it doesn't exist...
						return getOrCreateAndGetChildDocument(myAppContext,"/",containerId,blogContainer);
					}
				).then(
					function( result ) {
						blogContainer = result[0];
						console.log("blogContainer: %o",blogContainer);

						yearContainer = {
			    			title: pubYear
				    	};

						return getOrCreateAndGetChildDocument(myAppContext,blogContainer.id,blogContainer.id + pubYear + "/",yearContainer);
					}
				).then(
					function( result ) {
						yearContainer = result[0];
						console.log("yearContainer: %o",yearContainer);

						monthContainer = {
				    			title: pubMonth
					    };
						
						// Create the month container if it doesn't exist
						return getOrCreateAndGetChildDocument(myAppContext,yearContainer.id,yearContainer.id + pubMonth + "/",monthContainer);
					}
				).then(
					function( result ) {
						monthContainer = result[0];
						
						console.log("monthContainer: %o",monthContainer);
						
						dayContainer = {
				    			title: pubDay
					    };

						// Create the day container if it doesn't exist
						return getOrCreateAndGetChildDocument(myAppContext,monthContainer.id,monthContainer.id + pubDay + "/",dayContainer);
					}
				).then(
					function( result ) {													
						dayContainer = result[0];
						
						console.log("dayContainer: %o",dayContainer);

						content = {
			    			title: contentTitle,
			    			label: contentTitle,
			    			body: contentBody,
			    			publishedDate: pubDate,
			    			types: ["http://example.org/ont/web#BlogPost"]
				    	};
												
						return getOrCreateAndGetChildDocument(myAppContext,dayContainer.id,dayContainer.id + contentSlug + "/",content);
					}
				).then(
					function( result ) {												
						content = result[0];
						console.log("Content: %o", content);
						
						document.getElementById(alertSuccess)
						
						var a = document.getElementById("alertSuccess"), aStyle = a.style;
						document.getElementById("alertSuccessDocId").nodeValue = content.id;
						
						var $alertLink = $("#alertSuccessDocId");
						$alertLink.text(content.id);
						$alertLink.attr("href", content.id)
						aStyle.display = "block";
						
					}
				).catch( 
						function( result ) {
							console.log("ERROR: %o", result);
						}
				);

			
		}

		var el = document.getElementById('submitButton');
		el.addEventListener("click", submitContent, false);
		
	})();
	
	</script>

</body>
</html>