Textpattern tips, tutorials and code snippets

Create your own custom UI in the Write tab

A super fun and cool tip! This one is for creating your own custom UI in the Write tab. Did you ever want to have:

  • Content Boxes
  • Content Tables
  • Video Playlists
  • Schedule

And other cool fields UI in the Write tab?

Here’s what it looks like…

Custom UI content panes

Video:

Video: Create your own custom UI in the Write tab
Create your own custom UI in the Write tab
Clicking play connects you with youtube.com

All that with only use of a single textarea field, which can be the body/excerpt or a custom field (must be a textarea, not a regular input text, due to character limit)

This tip may look a little bit long, but you gonna enjoy it!

Get started by inserting jQuery

It is all about JS, jQuery mostly, and we need to get it somehow into the Write tab, there are several ways to do this:

Option #1.
By creating a .js file and upload it to your server and then link to it in the txplib_head.php file
(located in the textpattern/lib folder)

Option #2
Install the rah_external_output plugin then for example create a form name rah_eo_my_custom_ui.js and link to it in the txplib_head.php file (located in the textpattern/lib folder)

There are some other ways (plugins) to insert JS into the write tab and the rest of the TXP pages, but that’s how I’m doing it.

CSS file and drag and drop feature

Now you will also need a CSS file, because we want it to look good after all. So go and create a stylesheet in the Styles tab in TXP, then link to it in the txplib_head.php file (located in the textpattern/lib folder) – the same as with with the .js file.

If you wish to have the drag & drop feature, then you need to also include the jQuery UI (the same as you did with the others two files).

Visit jQuery UI and download it with the Sortable component, you will also need to include these components: Core, Widget, Mouse, Sortable. incase and you are using the bot_image_upload plugin, you may have already included the jQuery IU, so you can skip this step.

Script links in the HEAD element

<script type="text/javascript" src="textpattern.js"></script>

<script type="text/javascript" src="jquery-ui-1.8.23.custom.min.js"></script>
<link rel="stylesheet" type="text/css" media="screen" href="../css.php?n=my_custom_ui" />
<script type="text/javascript" src="../?rah_external_output=my_custom_ui.js"></script>

And for the final preparations install the rah_repeat plugin.

For Textpattern v4.5 – install rah_repeat latest version, currently it’s 0.8.1

For Textpattern v4.4.1 – install rah_repeat v0.7

What this plugin does is to let you easily split the content for each item, you will soon understand what it’s all about. Now that the preparations are complete, the fun starts!

Creating 3 types of custom UI elements

  • Content Boxes
  • Content Tables
  • Video Playlists

Each UI has got 3 main JS functions that:

  1. Creates the UI HTML fields structure
  2. Grabs the content from the UI fields and copies it into the textarea field on the article publish / save action
  3. Gets the saved content from the textarea field and places it back into it’s place in the UI

Following is the JS code, I wrote comments for the first “Content Boxes” UI that you should read to understand what’s going on there. This is really simple JS jQuery code, nothing special.

The scenario for the custom UI elements

Creating
  • Creating the UI container element and position it
  • Create the fields with the HTML structure
  • Use jQuery UI “Sortable” plugin for drag&drop (optional)
Saving
  • Check each item to filter it out if empty
  • Get the content from the items
  • Set the textarea field value with all the content
Gating
  • Check the textarea field value for any content
  • Get the content
  • Insert each item content into it’s place

The Content Boxes code

Custom UI Content Boxes
// Content Boxes - Create the UI fields
function content_boxes(){

	// First, check if we already created the UI fields
	if($('.content_boxes').length == 0){

		// Create the UI container element and position it
		$('#main_content div.text:first p:last').before('<div class="my_custom_ui content_boxes" />');

		// Hide the container element for now until we will finish to create the fields
		$('.content_boxes').hide();

		// Create the fields and set the limit number (5)
		for(var i=1; i<=5; i++){

			// Set the HTML structure
			var box_fields = 
				'<div class="content_box cbx_'+i+' cbx_empty">'
					+'<div class="cbx_field_container"><lable>Field 1</lable><input type="text" class="cbx_field cbxf1" value="" /></div>'
					+'<div class="cbx_field_container"><lable>Field 2</lable><input type="text" class="cbx_field cbxf2" value="" /></div>'
					+'<div class="cbx_field_container"><lable>Field 3</lable><textarea class="cbx_field cbxf3"></textarea></div>'
					+'<i>move</i>'
				+'</div>';

			// Append all the fields to the container
			$('.content_boxes').append(box_fields);

		}

		// Use jQuery UI "Sortable" plugin for drag&drop
		$('.content_boxes').sortable({
			items: '.content_box',
			placeholder: 'cbx_placeholder',
			handle: 'i',
			opacity: 0.6
		});
	}

	// Call the get saved content function
	content_boxes_get_saved();
}


// Content Boxes - On publish / save function
function content_boxes_publish(){

	// Check all items to see if not empty
	// if so then remove the "empty" class and white spaces
	$('.content_boxes .content_box').each(function(){
		if($(this).find('input:first').val().replace(/ /g,'').length > 0){
			$(this).removeClass('cbx_empty');
			$(this).find('input, textarea').each(function(){
				$(this).val($.trim($(this).val()));
			});
		}
	});

	// Move all the empty items to the bottom of the list 
	$('.content_box.cbx_empty').appendTo($('.content_boxes'));

	// Get the content from the items
	// Use a separator between each item (~||~)
	// Use a separator between each item's fields/content (~|~)
	var content_boxes_data = '';
	$('.content_boxes .content_box').not('.cbx_empty').each(function(){
		var cbxf1 = $(this).find('.cbx_field.cbxf1').val();
		var cbxf2 = $(this).find('.cbx_field.cbxf2').val();
		var cbxf3 = $(this).find('.cbx_field.cbxf3').val();
		content_boxes_data += cbxf1+'~|~'+cbxf2+'~|~'+cbxf3+'~||~';
	});

	// Set the textarea field value with all the content (also removes the last separator)
	$('#body').val(content_boxes_data.slice(0, -4));
}


// Content Boxes - Get saved content function
function content_boxes_get_saved(){

	// Check the textarea field value for any content
	if($('#body').val().length > 0){

		// Get the content and split the items by the separator (~||~)
		var content_boxes_data = $('#body').val();
		content_boxes_data = content_boxes_data.split('~||~');

		// Loop for each item
		$(content_boxes_data).each(function(index, value){

			// Get the item element
			var cbx = $('.content_box:eq('+index+')');

			// Split the item contents by the separator (~|~)
			var cbx_data = value.split('~|~');

			// Insert each item content to it's place
			$(cbx).find('.cbx_field.cbxf1').val(cbx_data[0]);
			$(cbx).find('.cbx_field.cbxf2').val(cbx_data[1]);
			$(cbx).find('.cbx_field.cbxf3').val(cbx_data[2]);
		});
	}

	// After all has been done, show the UI
	$('.content_boxes').show();
}

The Content Table code

Content Tables
// Content Table
function content_table(){
	if($('.content_table').length == 0){
		$('#main_content div.text:first p:last').before('<ul class="my_custom_ui content_table"><li class="ctr ct_titles"><h4>Title 1</h4><h4>Title 2</h4><h4>Title 3</h4></li></ul>');
		$('.content_table').hide();

		for(var i=1; i<=10; i++){
			var ctr = 
				'<li class="ctr ctr_'+i+' ctr_empty">'
					+'<input type="text" class="ctr_field ctrf1" value="" />'
					+'<input type="text" class="ctr_field ctrf2" value="" />'
					+'<input type="text" class="ctr_field ctrf3" value="" />'
					+'<i>move</i>'
				+'</li>';
			$('.content_table').append(ctr);
		}

		$('.content_table').sortable({
			items: '.ctr',
			placeholder: 'ctr_placeholder',
			handle: 'i',
			opacity: 0.6
		});
	}

	content_table_get_saved();
}

function content_table_publish(){
	// Loop all rows without the first titles row
	$('.content_table .ctr:not(":first")').each(function(){
		if($(this).find('input:first').val().replace(/ /g,'').length > 0){
			$(this).removeClass('ctr_empty');
			$(this).find('input').each(function(){
				$(this).val($.trim($(this).val()));
			});
		}
	});

	$('.content_table .ctr.ctr_empty').appendTo($('.content_table'));

	var content_table_data = '';

	// Loop all rows without the first titles row
	$('.content_table .ctr:not(":first")').not('.ctr_empty').each(function(){
		var ctrf1 = $(this).find('.ctr_field.ctrf1').val();
		var ctrf2 = $(this).find('.ctr_field.ctrf2').val();
		var ctrf3 = $(this).find('.ctr_field.ctrf3').val();
		content_table_data += ctrf1+'~|~'+ctrf2+'~|~'+ctrf3+'~||~';
	});

	$('#body').val(content_table_data.slice(0, -4));
}

function content_table_get_saved(){
	if($('#body').val().length > 0){
		var content_table_data = $('#body').val();
		content_table_data = content_table_data.split('~||~');

		$(content_table_data).each(function(index, value){

			// Increase index by 1, to skip on the first titles row
			index = index+1;

			var ctr = $('.ctr:eq('+index+')');
			var ctr_data = value.split('~|~');

			$(ctr).find('.ctr_field.ctrf1').val(ctr_data[0]);
			$(ctr).find('.ctr_field.ctrf2').val(ctr_data[1]);
			$(ctr).find('.ctr_field.ctrf3').val(ctr_data[2]);
		});
	}

	$('.content_table').show();
}

The Video Playlist code

Video Playlists
// Video Playlist
var video_playlist_default_image = 'youtube_logo.jpg';

function video_playlist(){
	if($('.video_playlist').length == 0){
		$('#main_content div.text:first p:last').before('<div class="my_custom_ui video_playlist" />');
		$('.video_playlist').hide();

		for(var i=1; i<=10; i++){
			var vpi = 
				'<div class="vpi vpi_'+i+' vpi_empty">'
					+'<div class="vpi_url">'
						+'<div class="vpi_num">'+i+'</div>'
						+'<label>Youtube URL:</label>'
						+'<input type="text" class="vpi_url_field" value="" />'
					+'</div>'
					+'<div class="vpi_image">'
						+'<img src="'+video_playlist_default_image+'" alt="" />'
						+'<div class="vpi_image_nav">'
							+'<a href="#" class="vpi_image_prev">&laquo;</a>'
							+'<a href="#" class="vpi_image_next">&raquo;</a>'
							+'<span class="vpi_image_count"><span>1</span>/3</span>'
						+'</div>'
					+'</div>'
					+'<i>move</i>'
				+'</div>';
			$('.video_playlist').append(vpi);
		}

		$('.video_playlist').sortable({
			items: '.vpi',
			placeholder: 'vpi_placeholder',
			handle: 'i',
			opacity: 0.6,
			update: function(event, ui) {
				$('.video_playlist .vpi').each(function(){
					$(this).find('.vpi_num').html($(this).index()+1);
				});
			}
		});

		$('.video_playlist .vpi_image_nav').hide();

		$('.video_playlist .vpi_image_nav a').click(function(event){
			event.preventDefault();
			var vpi_vid = $(this).parents('.vpi').find('.vpi_url_field').val().replace('http://www.youtube.com/watch?v=','');
			var vpi_img = $(this).parents('.vpi').find('.vpi_image img').attr('src').slice(0,-4).split(vpi_vid+'/');
			vpi_img = parseInt(vpi_img[1]);
			if($(this).hasClass('vpi_image_next')){
				if(vpi_img == 3){vpi_img = 1;}else{vpi_img = vpi_img+1}
			}else{
				if(vpi_img == 1){vpi_img = 3;}else{vpi_img = vpi_img-1}
			}
			$(this).parents('.vpi').find('.vpi_image img').attr('src','https://img.youtube.com/vi/'+vpi_vid+'/'+vpi_img+'.jpg');
			$(this).parents('.vpi').find('.vpi_image_count span').html(vpi_img);
			return false;
		});
	}

	video_playlist_get_saved();
}

function video_playlist_publish(){
	$('.video_playlist .vpi').each(function(){
		if($(this).find('input').val().replace(/ /g,'').length > 0){
			$(this).removeClass('vpi_empty');
			$(this).find('input').each(function(){
				$(this).val($.trim($(this).val()));
			});
		}
	});

	$('.video_playlist .vpi.vpi_empty').appendTo($('.video_playlist'));

	$('.video_playlist .vpi').each(function(){
		$(this).find('.vpi_num').html($(this).index()+1);
	});

	var video_playlist_data = '';
	$('.video_playlist .vpi').not('.vpi_empty').each(function(){
		var vpi_id = $(this).find('.vpi_url_field').val();
		if(vpi_id.indexOf('v=') != -1){
			vpi_id = vpi_id.split('v=');
			vpi_id = vpi_id[1].slice(0,11);
		}else if(vpi_id.indexOf('.be/') != -1){
			vpi_id = vpi_id.split('.be/');
			vpi_id = vpi_id[1].slice(0,11);
		}

		var vpi_img = $(this).find('.vpi_image img').attr('src').slice(0,-4).split(vpi_id+'/');
		video_playlist_data += vpi_id+'~|~'+vpi_img[1]+'~||~';
	});

	$('#body').val(video_playlist_data.slice(0, -4));
}

function video_playlist_get_saved(){
	if($('#body').val().length > 0){
		var video_playlist_data = $('#body').val();
		video_playlist_data = video_playlist_data.split('~||~');

		$(video_playlist_data).each(function(index, value){
			var vpi = $('.vpi:eq('+index+')');
			var vpi_data = value.split('~|~');

			$(vpi).find('.vpi_url_field').val('http://www.youtube.com/watch?v='+vpi_data[0]);
			$(vpi).find('.vpi_image img').attr('src','https://img.youtube.com/vi/'+vpi_data[0]+'/'+vpi_data[1]+'.jpg');
			$(vpi).find('.vpi_image_count span').html(vpi_data[1]);
			$(vpi).find('.vpi_image_nav').show();
		});
	}

	$('.video_playlist').show();

	$('.vpi_url_field').each(function(){
		$(this).change(function(){video_playlist_change($(this).parents('.vpi').index());});
		$(this).keyup(function(){video_playlist_change($(this).parents('.vpi').index());});
	});
}

function video_playlist_change(vpi){
	vpi = vpi+1;
	var vid = $('.vpi_'+vpi).find('.vpi_url_field').val().replace(/ /g,'');
	if(vid.indexOf('v=') != -1){
		vid = vid.split('v=');
		vid = vid[1].slice(0,11);
	}else if(vid.indexOf('.be/') != -1){
		vid = vid.split('.be/');
		vid = vid[1].slice(0,11);
	}
	if(vid.length == 11){
		$('.vpi_'+vpi).find('.vpi_url_field').val('http://www.youtube.com/watch?v='+vid).removeClass('vpi_error');
		$('.vpi_'+vpi).find('.vpi_image img').attr('src','http://img.youtube.com/vi/'+vid+'/1.jpg');
		$('.vpi_'+vpi).find('.vpi_image_nav').show();
		$('.vpi_'+vpi).find('.vpi_image_count span').html('1');
	}else if(vid.length == 0){
		$('.vpi_'+vpi).find('.vpi_url_field').removeClass('vpi_error');
		$('.vpi_'+vpi).find('.vpi_image_nav').hide();
		$('.vpi_'+vpi).find('.vpi_image img').attr('src',video_playlist_default_image);
	}else{
		$('.vpi_'+vpi).find('.vpi_url_field').addClass('vpi_error');
		$('.vpi_'+vpi).find('.vpi_image_nav').hide();
		$('.vpi_'+vpi).find('.vpi_image img').attr('src',video_playlist_default_image);
	}
}

Calling the functions

Now all this is good, but we need to call these functions somehow.

  • We want all this to run only in the Write tab
  • Check which section we are in
  • By default hide any custom IU
  • Create the right UI base on section
  • Save the right content from the UI on publish / save article based on the section
$(document).ready(function(){

	// On Write page
	if($('#page-article').length > 0){

		// On section change
		$('#section').change(function(){

			// By default hide any custom IU
			$('.my_custom_ui').hide();

			// By default show the fields that we are hide in a custom UI
			$('p.body, p.excerpt').show();

			// On "Content Boxes" (boxes) section
			if($(this).val() == 'boxes'){
				$('p.body, p.excerpt').hide();
				content_boxes();
			}

			// On "Content Table" (ctable) section
			if($(this).val() == 'ctable'){
				$('p.body, p.excerpt').hide();
				content_table();
			}

			// On "Video Playlist" (vplaylist) section
			if($(this).val() == 'vplaylist'){
				$('p.body, p.excerpt').hide();
				video_playlist();
			}

		}).change();


		// On publish / save an article
		$('#article-col-2 .publish:first').click(function(event){

			// On "Content Boxes" (boxes) section
			if ($('#section').val() == 'boxes'){
				content_boxes_publish();
			}

			// On "Content Table" (ctable) section
			if ($('#section').val() == 'ctable'){
				content_table_publish();
			}

			// On "Video Playlist" (vplaylist) section
			if ($('#section').val() == 'vplaylist'){
				video_playlist_publish();
			}

			// You can change this to a "Switch" statement if you like to
			/*
				switch($('#section').val()){
					case 'boxes'    : content_boxes_publish();  break;
					case 'ctable'   : content_table_publish();  break;
					case 'vplaylist': video_playlist_publish(); break;
				}
			*/
		});
	}

});

Now that we have finished with the UI, we want somehow to output the content into the website. That’s where the rah_repeat plugin that we installed before comes into place. This plugin lets us loop and split the content, converting it into Textpattern variables that we then can use as we wish.

For TXP v4.5 using rah_repeat v0.8.1 there is an easier way to do that using the “assign” attrubute.

For TXP v4.4.1 using rah_repeat v7.0 you’ll need to use the “limit” and “offset” attributes to separate the content and create the variables.

Following are the code blocks that go into your pages or forms to display the content – leave only the code that’s right for your TXP version.

For Content Boxes

<txp:article>

	For TXP v4.5 & rah_repeat v0.8.1
	<txp:rah_repeat value='<txp:body />' delimiter="~||~">
		<txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" assign="cbx_field1, cbx_field2, cbx_field3" />

		<div class="content_box">
			<h2><txp:variable name="cbx_field1" /></h2>
			<h3><txp:variable name="cbx_field2" /></h3>
			<p><txp:variable name="cbx_field3" /></p>
		</div>

	</txp:rah_repeat>


	For TXP v4.4.1 & rah_repeat v0.7
	<txp:rah_repeat value='<txp:body />' delimiter="~||~">
		<txp:variable name="cbx_field1"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>
		<txp:variable name="cbx_field2"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1" offset="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>
		<txp:variable name="cbx_field3"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1" offset="2"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>

		<div class="content_box">
			<h2><txp:variable name="cbx_field1" /></h2>
			<h3><txp:variable name="cbx_field2" /></h3>
			<p><txp:variable name="cbx_field3" /></p>
		</div>
	</txp:rah_repeat>

</txp:article>

For Content Tables

<txp:article>

	<h3><txp:title /></h3>

	<table cellspacing="2" cellpadding="2" border="1">

		For TXP v4.5 & rah_repeat v0.8.1
		<txp:rah_repeat value='<txp:body />' delimiter="~||~">
			<txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" assign="ctr_field1, ctr_field2, ctr_field3" />

			<tr class="ctr">
				<td><txp:variable name="ctr_field1" /></td>
				<td><txp:variable name="ctr_field2" /></td>
				<td><txp:variable name="ctr_field3" /></td>
			</tr>
		</txp:rah_repeat>


		For TXP v4.4.1 & rah_repeat v0.7
		<txp:rah_repeat value='<txp:body />' delimiter="~||~">
			<txp:variable name="ctr_field1"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>
			<txp:variable name="ctr_field2"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1" offset="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>
			<txp:variable name="ctr_field3"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1" offset="2"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>

			<tr class="ctr">
				<td><txp:variable name="ctr_field1" /></td>
				<td><txp:variable name="ctr_field2" /></td>
				<td><txp:variable name="ctr_field3" /></td>
			</tr>
		</txp:rah_repeat>

	</table>
</txp:article>

For YouTube Video Playlist

<txp:article>

	<div class="playlist">
		<h3><txp:permlink><txp:title /></txp:permlink></h3>

		For TXP v4.5 & rah_repeat v0.8.1
		<txp:rah_repeat value='<txp:body />' delimiter="~||~">
			<txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" assign="vpi_url, vpi_image" />

			<div class="item">
				<h6><a href="http://www.youtube.com/watch?v=<txp:variable name="vpi_url" />">http://www.youtube.com/watch?v=<txp:variable name="vpi_url" /></a></h6>
				<img src="https://img.youtube.com/vi/<txp:variable name="vpi_url" />/<txp:variable name="vpi_image" />.jpg" alt="" />
				<iframe width="200" height="150" src="http://www.youtube.com/embed/<txp:variable name="vpi_url" />" frameborder="0" allowfullscreen></iframe>
			</div>
		</txp:rah_repeat>


		For TXP v4.4.1 & rah_repeat v0.7
		<txp:rah_repeat value='<txp:body />' delimiter="~||~">
			<txp:variable name="vpi_url"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>
			<txp:variable name="vpi_image"><txp:rah_repeat value='<txp:rah_repeat_value />' delimiter="~|~" limit="1" offset="1"><txp:rah_repeat_value /></txp:rah_repeat></txp:variable>

			<div class="item">
				<h6><a href="http://www.youtube.com/watch?v=<txp:variable name="vpi_url" />">http://www.youtube.com/watch?v=<txp:variable name="vpi_url" /></a></h6>
				<img src="https://img.youtube.com/vi/<txp:variable name="vpi_url" />/<txp:variable name="vpi_image" />.jpg" alt="" />
				<iframe width="200" height="150" src="http://www.youtube.com/embed/<txp:variable name="vpi_url" />" frameborder="0" allowfullscreen></iframe>
			</div>
		</txp:rah_repeat>

	</div>

</txp:article_custom>

Last step! Add CSS styling to the boxes

Here’s the CSS for these 3 examples. It’s for styling the UI in the Write tab, not the output in your site pages. Add it to the stylesheet we created before in the beginning.

/* Content Boxes */

.content_boxes .content_box {
	width: 450px;
	position: relative;
	height: 120px;
	padding: 10px;
	margin: 20px 0;
	font-family: arial,sans-serif;
	background: #fff;
	border: 1px solid #ccc;
}

.content_boxes .content_box:hover {
	border: 1px dashed #999;
}

.content_boxes .content_box i {
	width: 16px;
	height: 16px;
	display: block;
	position: absolute;
	top: 5px;
	right: -22px;
	overflow: hidden;
	text-indent: -999px;
	background: url('textpattern/txp_img/move_icon.png') no-repeat 0 0;
	background-size: 16px 16px;
	cursor: move;
	opacity: 0.4;
}

.content_boxes .content_box i:hover {
	opacity: 1;
}

.content_boxes .cbx_placeholder {
	width: 450px;
	height: 120px;
	background: #f9f9f9;
	border: 1px dashed #ccc;
}

.content_boxes .cbx_field_container {
	height: 30px;
}

.content_boxes .content_box lable,
.content_boxes .content_box input,
.content_boxes .content_box textarea {
	float: left;
	display: block;
}

.content_boxes .content_box lable {
	width: 70px;
}

.content_boxes .cbx_field {
	width: 370px;
}

.content_boxes .cbxf3 {
	height: 37px;
	resize: none;
}

.content_boxes .cbxf3:hover,
.content_boxes .cbxf3:focus {
	color: #000;
}



/* Content Table */

.content_table,
.content_table li {
	list-style: none;
	padding: 0;
	margin: 0;
}

.content_table .ctr {
	width: 470px;
	height: 24px;
	position: relative;
	margin: 5px 0;
}

.content_table .ct_titles h4 {
	display: inline-block;
	width: 150px;
	margin-right: 5px;
}

.content_table .ctr input {
	width: 150px;
	margin-right: 5px;
}

.content_table i {
	width: 16px;
	height: 16px;
	display: block;
	position: absolute;
	top: 2px;
	right: -10px;
	overflow: hidden;
	text-indent: -999px;
	background: url('textpattern/txp_img/move_icon.png') no-repeat 0 0;
	background-size: 16px 16px;
	cursor: move;
	opacity: 0.4;
}

.content_table .ctr_placeholder {
	width: 450px;
	height: 30px;
	background: #f9f9f9;
	border: 1px dashed #ccc;
	margin: 5px 0;
}



/* Video Playlist */

.video_playlist .vpi {
	width: 454px;
	height: 120px;
	position: relative;
	border: 1px solid #ccc;
	padding: 5px;
	margin: 10px 0;
	background: #ffffff;
	background: -moz-linear-gradient(top,  #ffffff 0%, #f6f6f6 47%, #ededed 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed));
	background: -webkit-linear-gradient(top,  #ffffff 0%,#f6f6f6 47%,#ededed 100%);
	background: -o-linear-gradient(top,  #ffffff 0%,#f6f6f6 47%,#ededed 100%);
	background: -ms-linear-gradient(top,  #ffffff 0%,#f6f6f6 47%,#ededed 100%);
	background: linear-gradient(to bottom,  #ffffff 0%,#f6f6f6 47%,#ededed 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 );
}

.video_playlist .vpi_url label {
	font-weight: bold;
	color: #555;
}

.video_playlist .vpi_url {
	width: 314px;
	float: left;
}

.video_playlist .vpi_num {
	background: none repeat scroll 0 0 #FFFFFF;
	border-radius: 2px 2px 2px 2px;
	box-shadow: 1px 1px 0 #999999 inset;
	font: 12px/16px arial,sans-serif;
	height: 16px;
	margin-bottom: 10px;
	text-align: center;
	text-shadow: 1px 1px 0 #F1F1F1;
	width: 20px;
}

.video_playlist .vpi_url_field {
	width: 304px;
	display: block;
}

.video_playlist .vpi_error,
.video_playlist .vpi_error:hover,
.video_playlist .vpi_error:focus {
	border: 1px solid #f00;
	box-shadow: 0 0 2px #f00 !important;
}

.video_playlist .vpi_image {
	width: 126px;
	float: right;
}

.video_playlist .vpi_image img {
	width: 120px;	
	height: 90px;
	display: block;
	padding: 2px;
	border: 1px dashed #ccc;
	margin-bottom: 6px;
}

.video_playlist i {
	width: 16px;
	height: 16px;
	display: block;
	position: absolute;
	top: 2px;
	right: -20px;
	overflow: hidden;
	text-indent: -999px;
	background: url('textpattern/txp_img/move_icon.png') no-repeat 0 0;
	background-size: 16px 16px;
	cursor: move;
	opacity: 0.4;
}

.video_playlist .vpi_image_count {
	width: 30px;
	height: 18px;
	display: block;
	float: right;
	font: 12px/17px arial,sans-serif;
	text-align: right;
	padding-right: 5px;
}

.video_playlist .vpi_image_nav a {
	width: 24px;
	height: 14px;
	display: block;
	float: left;
	color: #555;
	background: #fafafa;
	background: -moz-linear-gradient(top,  #fafafa 0px, #dcdcdc 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0px,#fafafa), color-stop(100%,#dcdcdc));
	background: -webkit-linear-gradient(top,  #fafafa 0px,#dcdcdc 100%);
	background: -o-linear-gradient(top,  #fafafa 0px,#dcdcdc 100%);
	background: -ms-linear-gradient(top,  #fafafa 0px,#dcdcdc 100%);
	background: linear-gradient(to bottom,  #fafafa 0px,#dcdcdc 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fafafa', endColorstr='#dcdcdc',GradientType=0 );
	border: 1px solid;
	border-color: #ccc #ccc #aaa;
	box-shadow: 0 0 1px #fff inset;
	text-shadow: 0 1px 0 #fff;
	font: bold 16px/13px arial, sans-serif;
	text-align: center;
	text-decoration: none;
	outline: none;
}

.video_playlist .vpi_image_prev {
	border-radius: 4px 0 0 4px;
	margin-right: 5px;
}

.video_playlist .vpi_image_next {
	border-radius: 0 4px 4px 0;
}

.video_playlist .vpi_image_nav a:hover {
	background: #f0f0f0;
	background: -moz-linear-gradient(top,  #f0f0f0 0px, #e6e6e6 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0px,#f0f0f0), color-stop(100%,#e6e6e6));
	background: -webkit-linear-gradient(top,  #f0f0f0 0px,#e6e6e6 100%);
	background: -o-linear-gradient(top,  #f0f0f0 0px,#e6e6e6 100%);
	background: -ms-linear-gradient(top,  #f0f0f0 0px,#e6e6e6 100%);
	background: linear-gradient(to bottom,  #f0f0f0 0px,#e6e6e6 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f0f0', endColorstr='#e6e6e6',GradientType=0 );
	border-color: #aaa #aaa #999;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #fff inset;
}

.video_playlist .vpi_placeholder {
	width: 480px;
	height: 120px;
	background: #f9f9f9;
	border: 1px dashed #ccc;
}

That’s all, this is a very basic usage of what you can make, it’s an example, now it’s your turn to take it to the next level.

If you haven’t watched this tip video, then you should, you will get much clearer picture on how this all works and what it looks like.

Video: Create your own custom UI in the Write tab
Create your own custom UI in the Write tab
Clicking play connects you with youtube.com

I hope you like it, and please let me know what you think. If you do use it and create something cool, then share a print-screen with us :)

1 Comment Comment feed

Great tip thanks a lot!

Add a comment

Use Textile help to style your comments