christiansalazar avatar christiansalazar committed 01b6d34

first release

Comments (0)

Files changed (9)

+<?php
+ /**
+ * DippyAction class file.
+ *
+ *
+ *	@example:
+ *		public function actions() { 
+ *			return array(
+ 				'dippy'=>array('class'=>'ext.dippy.DippyAction')
+			); 
+ *		}
+ *
+ * @author Christian Salazar <christiansalazarh@gmail.com>
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class DippyAction extends CAction {
+	/**
+	* this action invokes the appropiated handler referenced by a 'classname' url attribute.
+	* the specified classname must implements: EYuiActionRunnable.php
+	*/
+	public function run(){
+		//Yii::log('ACTION CALLED','info');
+		include_once(dirname(__FILE__).'/DippyWidget.php');
+		$inst = new DippyWidget();
+		$inst->runAction($_GET['action']);
+	}
+ }
+
+<?php
+class DippyWidget extends CWidget {
+
+	public $id;
+	public $title='';
+	public $controllerName='site';
+	public $actionName = 'dippy';
+	public $parent = '';	// parent control ID, when it change dippy reply
+	public $logid = '';
+	public $modelName = '';	// example: 'Flavor'
+	public $parentKey; 		// example: 'categoryid' (flavor belongs to catgid)
+	public $attribute;		// example: 'flavor_name'
+
+	public $newItemLabel = 'New Item';
+	public $deleteConfirmation = 'Please confirm the item deletion.';
+	public $enterSaveText = 'Press Enter for saving';
+	public $validateErrorText = 'Please type a valid value';
+	public $validateRegExp = '';
+
+	public $onSuccess;
+	public $onError;
+	private $_baseUrl;
+
+	public function init(){
+		parent::init();
+		if($this->id == null)
+			$this->id = 'dippy0';
+		if($this->onSuccess == null)
+			$this->onSuccess = "function(){}";
+		if($this->onError == null)
+			$this->onError = "function(){}";
+	}
+
+	public function run(){
+		$this->_prepareAssets();
+		$loading = $this->_baseUrl.'/loading.gif';
+		$delete  = $this->_baseUrl.'/delete.png';
+		$update  = $this->_baseUrl.'/update.png';
+		
+		echo 
+"
+<div id={$this->id} class='dippy'>
+	<div class='dcontrol'>
+		<span>{$this->title}</span>
+		<a class='newItem'>{$this->newItemLabel}<img src='{$loading}'></a>
+	</div>
+</div>
+";
+
+		$options = CJavaScript::encode(array(
+			'id'=>$this->id,
+			'action'=>CHtml::normalizeUrl(array(
+				$this->controllerName.'/'.$this->actionName)),
+			'imgDelete'=>$delete,
+			'imgUpdate'=>$update,
+			'imgWait' =>$loading,
+			'logid'=>$this->logid,
+			'onSuccess'=>new CJavaScriptExpression($this->onSuccess),
+			'onError'=>new CJavaScriptExpression($this->onError),
+			'parent'=>$this->parent,
+			'deleteconfirmation'=>$this->deleteConfirmation,
+			'enterSaveText'=>$this->enterSaveText,
+			'validateErrorText'=>$this->validateErrorText,
+			'validateRegExp'=>$this->validateRegExp,
+			'data'=>serialize(array(
+				'modelName'=>$this->modelName,
+				'parentKey'=>$this->parentKey,
+				'attribute'=>$this->attribute,
+			)),
+		));
+		Yii::app()->getClientScript()->registerScript($this->id
+				,"new Dippy({$options})");
+
+	}// end run()
+	
+	
+	public function _prepareAssets(){
+		$localAssetsDir = dirname(__FILE__) . '/assets';
+		$this->_baseUrl = Yii::app()->getAssetManager()->publish(
+				$localAssetsDir);
+        $cs = Yii::app()->getClientScript();
+        $cs->registerCoreScript('jquery');
+		foreach(scandir($localAssetsDir) as $f){
+			$_f = strtolower($f);
+			if(strstr($_f,".swp"))
+				continue;
+			if(strstr($_f,".js"))
+				$cs->registerScriptFile($this->_baseUrl."/".$_f);
+			if(strstr($_f,".css"))
+				$cs->registerCssFile($this->_baseUrl."/".$_f);
+		}
+	}
+
+
+	/**
+	 * runAction
+	 *	invoked from DippyAction
+	 * 
+	 * @param mixed $action 
+	 * @access public
+	 * @return void
+	 */
+	public function runAction($action){
+		$data = '';
+		$modelName = '';
+		if(isset($_GET['data'])){
+			$data = unserialize($_GET['data']);
+			$modelName = $data['modelName'];
+			$parentKey = $data['parentKey'];
+			$attribute = $data['attribute'];
+		}
+
+		if($action == 'refresh'){
+			$parent = $_GET['parent'];
+			$model = new $modelName;
+			$a = array();
+			foreach($model->findAllByAttributes(array(
+				$parentKey=>$parent)) as $m)
+					$a[$m->primarykey] = $m[$attribute];
+			header('Content-Type: text/json');
+			echo CJSON::encode($a);
+		}
+
+		if($action == 'newitem'){
+			$parent = $_GET['parent'];
+			$model = new $modelName;
+			$model[$parentKey] = $parent;
+			$model[$attribute] = 'new item';
+			if($model->save()){ // using Save, to get validation
+				header('Content-Type: text/json');
+				echo CJSON::encode(array(
+					'id'=>$model->primarykey, 
+					'text'=>$model[$attribute]
+				));
+			}else
+			throw new Exception(CHtml::errorSummary($model));
+		}
+
+		if($action == 'delete'){
+			$id = $_GET['id'];
+			$model = new $modelName;
+			$inst = $model->model()->findByPk($id);
+			if($inst != null)
+				if($inst->delete()){
+					echo "OK";
+					return;
+				}
+			throw new Exception("cannot delete");
+		}	
+
+		if($action == 'update'){
+			$id = $_GET['id'];
+			$val = trim(file_get_contents('php://input'));
+			$model = new $modelName;
+			$inst = $model->model()->findByPk($id);
+			if($inst != null){
+				$inst[$attribute] = $val;
+				if($inst->update()){
+					echo $inst[$attribute];
+					return;
+				}
+			}
+			throw new Exception("cannot update");
+		}	
+
+
+	}
+}
+Dippy Widget
+============
+
+by:
+
+Christian Salazar. christiansalazarh@gmail.com	@yiienespanol, dic. 2012.
+
+![Screen Capture]
+(https://bitbucket.org/christiansalazarh/dippy/downloads/screenshot.png "Screen Capture")
+
+[http://opensource.org/licenses/bsd-license.php](http://opensource.org/licenses/bsd-license.php "http://opensource.org/licenses/bsd-license.php")
+
+[Repository at Bit Bucket !](https://bitbucket.org/christiansalazarh/dippy/ 
+ "Repository at Bit Bucket !")
+
+#Requirement: 
+
+Yii  1.1.12
+
+
+#What it does ?
+
+[EN]
+This widget helps you when dependent objects are required on your model,
+making it easy for you (as user) to create master-detail items.  
+Every "Dippy" widget can work togheter with another 'Dippy' widget on the same 
+view, in cascade, the second Dippy widget listen for changes in the first 
+Dippy widget, when a change occurs in the first widget a 'refresh' event is 
+fired for the second 'Dippy' Widget.
+
+Example use case:
+
+Suppose you have an article, it needs some attributes specified by the user, so
+you can use a Dippy widget #1 in wich the user creates: "Flavor", "Package" 
+items, and so on. 
+As you know, many flavors exists, your user wants to insert each
+flavor and each package, to achive this task you can insert a new Dippy 
+Widget #2 in the same view.
+in the second Dippy Widget your user inserts the Flavors..Peach, Orange, each
+one associated with a parent: The seleceted Flavor in Dippy Widget #1. When
+your user select another item in Dippy Widget #1 the widget #2 automatically
+refresh and show associated content.
+
+[ES]
+Este widget te ayuda cuando necesitas crear objetos dependientes en tu modelo,
+haciendo que la creacion de objetos sea facil para el usuario en un escenario
+maestro-detalle.
+Cada widget 'Dippy' puede trabajar concatenado con otro Widget 'Dippy' en la
+misma vista, en cascada, el segundo widget Dippy estara atento a los cambios
+de seleccion del primero Widget causando un evento 'refresh' cuando hay
+cambio de seleccion en el primer widget.
+
+Caso de ejemplo:
+
+Para un articulo que se vende en internet, necesitas indicar atributos,
+por ejemplo: 'Sabor', 'Empaque'.  El primer Widget Dippy te permite crear
+estos items, asociandolos a un Articulo (su maestro).
+
+Un segundo widget Dippy, podrá usarse para crear los Sabores, y los Empaques,
+dependiendo de si se selecciono 'Sabor' o 'Empaque' en el primer widget, 
+cuando la seleccion cambia en el primer Widget el segundo se actualiza.  Al
+crea un nuevo 'Sabor' este dato quedara enlazado con su maestro, por ejemplo,
+'Peach', 'Orange' son sabores que el usuario va insertando.
+
+#Usage
+
+## Insert and configure the Widget.
+
+~~~
+[php]
+~~~
+
Added
New image
+.dippy {
+	border: 1px dotted #ddd;
+	padding: 3px;
+	margin: 3px;
+	background-color: white;
+}
+.dippy .dcontrol {
+	background-color: #ddd;
+	margin-bottom: 3px;
+	padding: 3px;
+}
+.dippy .dcontrol a.newItem {
+	color: blue;
+	font-size: 11px;
+	cursor: pointer;
+}
+.dippy .dcontrol a.newItem img {
+	display: none;
+}
+
+.dippy .drow {
+	padding: 3px;
+	margin-bottom: 3px;
+	overflow: auto;
+	cursor: pointer;
+	border-radius: 3px;
+	border: 1px dotted #666;
+}
+.dippy .drow img.wait {
+	display: none;
+}
+
+.drow-selected {
+	background-color: #ddff99;
+}
+
+.dippy .drow-removed {
+	background-color: #eee;
+	padding: 3px;
+	margin-bottom: 3px;
+	overflow: auto;
+	font-style: italic;
+}
+.dippy .drow-removed img.ddelete, 
+.dippy .drow-removed img.dupdate 
+{
+	display: none;
+}
+.dippy .drow-removed img.wait {
+	display: visible;
+}
+
+.dippy .drow-edit {
+	background-color: #ddffaa;
+	padding: 3px;
+	margin-bottom: 3px;
+	overflow: auto;
+	font-style: italic;
+}
+.dippy .drow-edit img.ddelete, 
+.dippy .drow-edit img.dupdate 
+{
+	display: none;
+}
+.dippy .drow-edit img.wait {
+	display: visible;
+}
+.dippy .drow-edit input {
+	background-color: #eee;
+	color: gray;
+}
+
+.dippy .ddelete {
+   	float: right;
+	cursor: pointer;
+}
+
+.dippy .dupdate {
+	float: left;
+	margin-right: 3px;
+	cursor: pointer;
+}
+
+
+var Dippy = function(options){
+	var div = $('#'+options.id);
+	this.change = function(next, prior){  
+		//alert('original '+options.id); 
+	};
+
+	var logger = function(text){
+		var d = $('#'+options.logid);
+		if(d != null)
+			d.append("<p style='border: 1px dotted red; font-size: 10px; color: darkred; font-family: monospace'>"
+				+text+"</p>");
+	}
+
+	var ajaxcmd = function(action,postdata,callback){
+		var result=false;
+		var nocache=function(){
+			var dateObject = new Date();
+			var uuid = dateObject.getTime();
+			return "&nocache="+uuid;
+		}
+		jQuery.ajax({
+			url: action+nocache(),
+			type: 'post',
+			async: true,
+			contentType: "application/json",
+			data: postdata,
+			success: function(data, textStatus, jqXHR){
+				result = data;
+				if(callback != null){
+					logger('true. '+data);
+					callback(true, data);
+				}
+			},
+			error: function(jqXHR, textStatus, errorThrown){
+				logger('false. '+jqXHR.responseText);
+				callback(false, jqXHR.responseText);
+				return false;
+			},
+		});
+		return result;
+	}
+
+	var onDelete = function(id, item){
+		logger("onDelete "+id+" ");
+		var parent = div.data('parent');
+		if(confirm(options.deleteconfirmation+', item: '+id)){
+			item.removeClass('drow');
+			item.addClass('drow-removed');
+			ajaxcmd(options.action+'&action=delete&parent='+parent+'&id='+id
+					+'&data='+options.data,
+				'',function(ok,data){
+					if(ok){
+						item.remove();
+					}else{
+						item.addClass('drow');
+						item.removeClass('drow-removed');
+					}
+				});
+		}
+	}
+
+	var validate = function(v){
+		if(options.validateRegExp == '')
+			return true;
+
+		return true;
+	}
+
+	var onUpdate = function(id, item){
+		var span = item.find('span.text');
+		if(span.data('busy')==true){
+			span.html(span.data('saved'));
+			span.data('busy',false);
+			return;
+		}	
+
+		var text = span.html();
+		span.data('busy',true);
+		span.data('saved',text);
+		span.html("");
+		span.append("<input class='drow-text' type='text' value='"+text+"'>");
+		var input = span.find('input');
+		input.attr('title',options.enterSaveText);
+		input.keyup(function(){
+			if(event.keyCode == 13){
+				var value = jQuery.trim($(this).val());	
+				if(validate(value)){
+					item.removeClass("drow");
+					item.addClass("drow-edit");
+					input.attr('disabled','disabled');
+
+					ajaxcmd(options.action+'&action=update&parent='+parent
+						+'&id='+id+'&data='+options.data,
+						value,function(ok,data){
+							if(ok){
+								span.html(data);
+								span.data('busy',false);
+								item.removeClass('drow-edit');
+								item.addClass('drow');
+							}else{
+								item.removeClass('drow-edit');
+								item.addClass('drow');
+								input.attr('disabled',null);
+							}
+						});
+
+				}else{
+					alert(options.validateErrorText);
+				}
+			}
+		});
+	}
+
+	var newItem = function(id, label){
+		div.append( 
+		"<div alt='"+id+"' class='drow'>"+
+		"<img class='wait' src='"+options.imgWait+"'>"+
+		"<span class='text' title='"+id+"'>"+label+"</span>"+
+		"<img class='ddelete' src='"+options.imgDelete+"' >"+
+		"<img class='dupdate' src='"+options.imgUpdate+"' >"+
+		"</div>");
+
+		var item = div.find("[alt|='"+id+"']");
+
+		item.data('_this',this);
+
+		item.find('img.ddelete').click(function(){ onDelete(item.attr('alt'), 
+			item) });
+
+		item.find('img.dupdate').click(function(){ onUpdate(item.attr('alt'), 
+			item) });
+
+		item.click(function(){
+			var ci = div.data('current_item');
+			var id = $(this).attr('alt');
+			if(ci != id){
+				var old_id = div.data('current_item');
+				var prior = div.find("[alt|='"+old_id+"']");
+				var item  = $(this);
+				div.data('current_item',id);
+				item.addClass('drow-selected');	
+				prior.removeClass('drow-selected');
+				var _div = item.parent();
+				var _this = _div.data('_this');
+				_this.change(item, prior);
+			}
+		});
+	}
+	
+	var clearDiv = function(){
+		div.find('.drow').each(function(){ $(this).remove(); });
+	}
+
+
+	var refresh = function(id){
+		logger('refresh: '+id);
+		div.data('parent',id);
+		
+		clearDiv();
+		
+		ajaxcmd(options.action+'&action=refresh&parent='+id
+				+'&data='+options.data,
+			'',function(ok,data){
+				if(ok){
+					$.each(data,function(k, v){
+						newItem(k,v);
+					});
+				}else{
+
+				}
+			});
+	}
+
+	div.find('.dcontrol a.newItem').click(function(){
+		var id = div.data('parent');
+		var a = $(this);
+		if(a.data('busy')==true)
+			return;
+		a.find('img').show();
+		a.css('color','gray');
+		a.data('busy',true);
+		ajaxcmd(options.action+'&action=newitem&parent='+id
+				+'&data='+options.data,
+			'',function(ok,data){
+				a.find('img').hide();
+				a.css('color','blue');
+				a.data('busy',false);
+				if(ok){
+					newItem(data.id, data.text);	
+				}
+			});
+	});
+
+	div.data('is_dippy',true);
+	div.data('_this',this);
+
+	var parent = $('#'+options.parent);
+	if(parent.data('is_dippy') == true){
+		var _this = parent.data('_this');
+		_this.change = function(next, prior){
+			var parent_id = next.attr('alt');		
+			refresh(parent_id);
+		};
+	}else{
+		var value = parent.val();
+		parent.change(function(){
+			refresh($(this).val());		
+		});
+		refresh(value);
+	}
+
+
+};
Added
New image
Added
New image
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.