RSS
 

Archive for the ‘Java Script’ Category

Facebook – dodawanie aplikacji do fanpage-a

12 mar

Facebook to dla developera wieczne utrapienie. Ciągłe zmiany w interfejsie, w API, dodawanie coraz to nowych funkcjonalności czy choćby permanentny redesign zmuszają do bezustannego poprawiania napisanych aplikacji. Pisząc aplikacje na facebook średnio raz na pół roku muszę być przygotowany na to, że połowę rzeczy, które nauczyłem się ostatnio implementować teraz będę musiał zrobić w zupełnie inny sposób. Czytanie tutoriali, czy wskazówek na blogach często nie ma sensu gdyż zamieszczone porady są już dawno nieaktualne. Łapię się na tym, że napisanie średnio rozbudowanej aplikacji zajmuje mniej czasu niż opublikowanie i zintegrowanie jej z facebookiem.

Dzisiaj klient zgłosił mi, że nie można dodać aplikacji do fanpage-a gdyż nigdzie nie ma przycisku „Add to my page”. Zawsze jak się wchodziło na stronę aplikacji była możliwość dodania jej do fanpage-a za pomocą jednego kliknięcia, a później ewentualnie skonfigurowanie jej tak aby wyświetlała się w zakładce, a teraz nie ma. I co? I zaczęło się rycie w dokumentacji i googlach.

W końcu znalazłem przepis na to jak dodać analogiczny przycisk do kodu samej aplikacji

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:fb="https://www.facebook.com/2008/fbml">
  <head>
    <title>My Add to Page Dialog Page</title>
  </head>
  <body>
    <div id='fb-root'></div>
    <script src='http://connect.facebook.net/en_US/all.js'></script>
    <p><a onclick='addToPage(); return false;'>Add to Page</a></p>
    <p id='msg'></p>
 
    <script> 
      FB.init({appId: "YOUR_APP_ID", status: true, cookie: true});
 
      function addToPage() {
 
        // calling the API ...
        var obj = {
          method: 'pagetab',
          redirect_uri: 'YOUR_URL',
        };
 
        FB.ui(obj);
      }
 
    </script>
  </body>
</html>

Irytuje mnie ta polityka Facebooka niemiłosiernie ponieważ, kiedy coś przestaje działać, albo wyglądać na facebooku klient ma pretensje do mnie. Ja z kolei nie mam ochoty poprawek i modyfikacji wynikających ze zmiany flow na facebooku robić w ramach gwarancji bo to często nie są sprawy 5 minutowe.

 
 

Tworzenie obiektów w JavaScript

21 mar

W JavaSripcie istnieje bazowa klasa Object, która stanowi prototyp wszystkich pozostałych obiektów JavaScript. Można jej użyć bezpośrednio do utworzenia obiektu.

    obj = new Object();
    obj.x = 1;
    obj.y = 2;

lub alternatywnie

    obj = {};
    obj.x = 1;
    obj.y = 2;

Nawiasy klamrowe stanowią semantyczny skrót podobnie jak użycie nawiasów kwadratowych (arr = []) może zastąpić arr = new Array(). Tablica czyli Array jest jednym z wielu wbudowanych w JavaScript obiektów podobnie jak Window, Document itd.

Aby utworzyć niestandardowy obiekt należy najpierw zdefinować prototyp Klasy, której instancją dany obiekt będzie. Najprościej utworzyć prototyp metodą konstruktora.

var Foo = function() {
    this.x = 1;
    this.y = 2;
    this.sum = function() {
       return this.x + this.y;
    }
}

W konstruktorze definiujemy właściwości i metody. Nową instancję tworzy się z użyciem operatora new.

    obj = new Foo();
    alert(obj.x);
    alert(obj.sum());

Do właściwości lub metod można się odwołać za pośrednictwem utworzonego w ten sposób obiektu, ale można też pominąć operację przypisania do zmiennej i wywołać metodę w sposób niemożliwy np. w PHP.

	alert(new Foo().sum());

Wywołując obj = new Foo(); powołuje się do życia instancje prototypu Foo. Można następnie wywołać metodę obj.sum();. Metodę sum można też wywołać z pominięciem przypisania obiektu do zmiennej new Foo().someMethod(); Tak czy inaczej zawsze w trzeba w pierwszej kolejności utworzyć obiekt.

Definiowanie klas metodą prototypu jest łatwe i intuicyjne, nie mniej w przypadku powoływania wielu obiektów danego prototypu zalecany jest sposób definiowania metod z użyciem prototype (nie mylić z frameworkiem prototype).

var Foo = function() { 
    this.x = 1;
    this.y = 2;
};
 
Foo.prototype.sum = function() {
   return this.x + this.y;
}

Różnica polega na tym, że w pierwszym przypadku przy powoływaniu do życia nowej instancji za każdym razem metoda „sum” tworzona jest na nowo, a w drugim przypadku jest to cały czas jedna i ta sama metoda. Zyskuję się na zaoszczędzonej pamięci co przy bardzo rozbudowanych skryptach nabiera znaczenia.

Zdefiniowanie metody z pominięciem słowa „prototype”:

var Foo = function() { };
 
Foo.sum = function(x, y) {
    return x+ y;
}

… daje możliwość wywołania metody klasy bez tworzenia obiektu Foo.sum(2,4); – to taki jakby odpowiednik metod statycznych w PHP. Analogicznie do metod statycznych w PHP użycie „this” wewnątrz takiej funkcji mija się z celem, więc gdybyśmy przez przypadek zdefiniowali:

var Foo = function() { 
    this.x = 1;
    this.y = 2;
};
 
Foo.sum = function() {
   return this.x + this.y;
}

to wywołanie Foo.sum(); zwróci „NaN” a z kolei new Foo().sum(); zakończy się błędem „TypeError on line 1: (new Foo).sum is not a function”.

W JS można też utworzyć obiekt ad hock

var Foo = { x: 1, y: 2, sum: function () { return this.x + this.y; } } W tym wypadku Foo nie jest prototypem tylko instancją prototypu Object dlatego oczywistym jest wywołanie Foo.sum(); z pominięciem operatora new.

 
 

jsTree – context content

29 gru

Skryptów do wizualizacji struktur drzewiastych nie brakuje. W javascript-cie powstało tego cała masa. Ja preferuje rozwiązania oparte o jquery i nawet po przyjęciu tego zawężającego kryterium mam w czym wybierać. To też wypróbowałem parę rozwiązań i ostatecznie zdecydowałem się na jstree.

Po przejrzeniu wszystkich dem prezentujących możliwości tej aplikacji (Piszę aplikacji bo coś co ma np. pluginy – nie jest już prostym skryptem), a także dokumentacji jestem naprawdę pod wrażeniem. Rozwijanie i zwijanie katalogów to banał, ale do tego dochodzi możliwość przeciągania elementów lub nawet całych gałęzi, możliwość dodawania, edytowania, usuwania, kopiowania elementów. Wspomniany skrypt jest naprawdę dobrze napisany. Daje możliwość przechwycenia wszelkich zachodzących na drzewie wydarzeń i dowolnego ich obsłużenia. Łącząc takie technologie jak ajax i np. php możemy doczytywać rozwijane katalogi lub też zapisywać wszelkie wykonane na drzewie operacje takie jak utworzenie nowego elementu lub też przeniesienie gałęzi w inne miejsce.

Wspomniałem już o pluginach. Do dyspozycji mamy kilka przydatnych rozszerzeń. „Cookie plugin” np. umożliwia zapisanie stanu drzewa. Dzięki temu po przeładowaniu strony wszystkie rozwinięte wcześniej gałęzie nadal takimi pozostają i nie musimy bawić się w ponowne klikanie by dojść do elementu na trzecim poziomie. „Keyboard navigation” umożliwia poruszanie się po drzewie przy pomocy klawiatury, a „Checkbox plugin” umożliwia zaznaczenie więcej niż jednego elementu poprzez kliknięcie utworzonego przy nazwie elementu checkboxa.

Jednym z ciekawszych dodatków jest w pełni konfigurowalne menu kontekstowe. „Context menu plugin” ma już predefiniowane ustawienie umożliwiające wykonanie takich akcji jak utworzenie podelementu oraz usunięcie lub edycja elementu bieżącego. Demo dodatkowo prezentuje w jaki sposób dodać do niego własną akcję. Menu kontekstowe ma jedną wadę, która objawia się w operze. Ponieważ pojawia się po kliknięciu na prawym przycisku myszki w Operze menu kontekstowe nie zadziała gdyż wspomniana przeglądarka nie daje możliwości nadpisania akcji prawokliku. Z tego też powodu zdecydowałem się nieco zmodyfikować tenże plugin w ten sposób, że po wybraniu danego elementu przy jego nazwie pojawia się mały plusik, którego kliknięcie otwiera menu kontekstowe.

(function ($) {
	$.extend($.tree.plugins, {
		"contextmenu" : {
			context_menu : {
				object : $("<ul id='jstree-contextmenu' class='tree-context' />"),
					data : {
					t : false,
					a : false,
					r : false
				},
				isrgtclick: false,
				defaults : {
					class_name : "hover",
					items : {
						create : {
							label	: "Create", 
							icon	: "create",
							visible	: function (NODE, TREE_OBJ) { if(NODE.length != 1) return 0; return TREE_OBJ.check("creatable", NODE); }, 
							action	: function (NODE, TREE_OBJ) { TREE_OBJ.create(false, TREE_OBJ.get_node(NODE[0])); },
							separator_after : true
						},
						rename : {
							label	: "Rename", 
							icon	: "rename",
							visible	: function (NODE, TREE_OBJ) { if(NODE.length != 1) return false; return TREE_OBJ.check("renameable", NODE); }, 
							action	: function (NODE, TREE_OBJ) { TREE_OBJ.rename(NODE); } 
						},
						remove : {
							label	: "Delete",
							icon	: "remove",
							visible	: function (NODE, TREE_OBJ) { var ok = true; $.each(NODE, function () { if(TREE_OBJ.check("deletable", this) == false) ok = false; return false; }); return ok; }, 
							action	: function (NODE, TREE_OBJ) { $.each(NODE, function () { TREE_OBJ.remove(this); }); } 
						}
					}
				},
				show : function(obj, t, e) {
					var opts = $.extend(true, {}, this.defaults, t.settings.plugins.contextmenu);
					obj = $(obj);
					if(obj.size() == 0) return;
					this.data.t = t;
					if(!obj.children("a:eq(0)").hasClass("clicked")) {
						this.data.a = obj;
						this.data.r = true;
						obj.children("a").addClass(opts.class_name);
						if (e) {
							e.target.blur();
						}
					}
					else { 
						this.data.r = false; 
						this.data.a = (t.selected_arr && t.selected_arr.length > 1) ? t.selected_arr : t.selected;
					}
 
					this.object.empty();
					var str = "";
					var cnt = 0;
					for(var i in opts.items) {
						if(!opts.items.hasOwnProperty(i)) continue;
						if(opts.items[i] === false) continue;
						var r = 1;
						if(typeof opts.items[i].visible == "function") r = opts.items[i].visible.call(null, this.data.a, t);
						if(r == -1) continue;
						else cnt ++;
						if(opts.items[i].separator_before === true) str += "<li class='separator'><span>&nbsp;</span></li>";
						str += '<li><a href="#" rel="' + i + '" class="' + i + ' ' + (r == 0 ? 'disabled' : '') + '">';
						if(opts.items[i].icon) str += "<ins " + (opts.items[i].icon.indexOf("/") == -1 ? " class='" + opts.items[i].icon + "' " : " style='background-image:url(\"" + opts.items[i].icon + "\");' " ) + ">&nbsp;</ins>";
						else str += "<ins>&nbsp;</ins>";
						str += "<span>" + opts.items[i].label + '</span></a></li>';
						if(opts.items[i].separator_after === true) str += "<li class='separator'><span>&nbsp;</span></li>";
					}
					var tmp = obj.children("a:visible").offset();
					this.object.attr("class","tree-context tree-" + t.settings.ui.theme_name.toString() + "-context").html(str);
					var h = this.object.height();
					var w = this.object.width();
					var x = tmp.left;
					var y = tmp.top + parseInt(obj.children("a:visible").height()) + 2;
					var max_y = $(window).height() + $(window).scrollTop();
					var max_x = $(window).width() + $(window).scrollLeft();
					if(y + h > max_y) y = Math.max( (max_y - h - 2), 0);
					if(x + w > max_x) x = Math.max( (max_x - w - 2), 0);
					this.object.css({ "left" : (x), "top" : (y) }).fadeIn("fast");
 
					if (e) {
						e.preventDefault(); 
						e.stopPropagation();
						this.isrgtclick = true;
					}
				},
				hide : function (check_isrgtclick) {
					if (check_isrgtclick == true && this.isrgtclick == false) {
						return;
					}
					this.isrgtclick = false;
					if(!this.data.t) return;
					var opts = $.extend(true, {}, this.defaults, this.data.t.settings.plugins.contextmenu);
					if(this.data.r && this.data.a) {
						this.data.a.children("a, span").removeClass(opts.class_name);
					}
					this.data = { a : false, r : false, t : false };
					this.object.fadeOut("fast");
				},
				exec : function (cmd) {
					if($.tree.plugins.contextmenu.context_menu.data.t == false) return;
					var opts = $.extend(true, {}, $.tree.plugins.contextmenu.context_menu.defaults, $.tree.plugins.contextmenu.context_menu.data.t.settings.plugins.contextmenu);
					try { opts.items[cmd].action.apply(null, [$.tree.plugins.contextmenu.context_menu.data.a, $.tree.plugins.contextmenu.context_menu.data.t]); } catch(e) { };
				},
				add : function (n, t) {
					if ($('#jstree-show-contextmenu').length == 0) {
						$(n).children('a').after('<a id="jstree-show-contextmenu" href="#">+</a>');
						$('#jstree-show-contextmenu').click(function(){
							$.tree.plugins.contextmenu.context_menu.show(n, t);
						});
					}
				},
				rem : function () {
					if ($('#jstree-show-contextmenu').length > 0) {
						$('#jstree-show-contextmenu').remove();
					}
				}
			},
			callbacks : {
				oninit : function () {
					if(!$.tree.plugins.contextmenu.css) {
						var css = '#jstree-contextmenu { display:none; position:absolute; z-index:2000; list-style-type:none; margin:0; padding:0; left:-2000px; top:-2000px; } .tree-context { margin:20px; padding:0; width:180px; border:1px solid #979797; padding:2px; background:#f5f5f5; list-style-type:none; }.tree-context li { height:22px; margin:0 0 0 27px; padding:0; background:#ffffff; border-left:1px solid #e0e0e0; }.tree-context li a { position:relative; display:block; height:22px; line-height:22px; margin:0 0 0 -28px; text-decoration:none; color:black; padding:0; }.tree-context li a ins { text-decoration:none; float:left; width:16px; height:16px; margin:0 0 0 0; background-color:#f0f0f0; border:1px solid #f0f0f0; border-width:3px 5px 3px 6px; line-height:16px; }.tree-context li a span { display:block; background:#f0f0f0; margin:0 0 0 29px; padding-left:5px; }.tree-context li.separator { background:#f0f0f0; height:2px; line-height:2px; font-size:1px; border:0; margin:0; padding:0; }.tree-context li.separator span { display:block; margin:0px 0 0px 27px; height:1px; border-top:1px solid #e0e0e0; border-left:1px solid #e0e0e0; line-height:1px; font-size:1px; background:white; }.tree-context li a:hover { border:1px solid #d8f0fa; height:20px; line-height:20px; }.tree-context li a:hover span { background:#e7f4f9; margin-left:28px; }.tree-context li a:hover ins { background-color:#e7f4f9; border-color:#e7f4f9; border-width:2px 5px 2px 5px; }.tree-context li a.disabled { color:gray; }.tree-context li a.disabled ins { }.tree-context li a.disabled:hover { border:0; height:22px; line-height:22px; }.tree-context li a.disabled:hover span { background:#f0f0f0; margin-left:29px; }.tree-context li a.disabled:hover ins { border-color:#f0f0f0; background-color:#f0f0f0; border-width:3px 5px 3px 6px; }';
						$.tree.plugins.contextmenu.css = this.add_sheet({ str : css });
					}
				},
				onrgtclk : function (n, t, e) {
					$.tree.plugins.contextmenu.context_menu.show(n, t, e);
				},
				onselect : function(n, t) {
					$.tree.plugins.contextmenu.context_menu.rem();
					$.tree.plugins.contextmenu.context_menu.add(n, t);
				},
				ondeselect : function(n, t) {
					$.tree.plugins.contextmenu.context_menu.rem();
				},
				onchange : function () { 
					$.tree.plugins.contextmenu.context_menu.hide(true);
				},
				beforedata : function () {
					$.tree.plugins.contextmenu.context_menu.hide(true);
				},
				ondestroy : function () {
					$.tree.plugins.contextmenu.context_menu.hide(true);
				}
			}
		}
	});
	$(function () {
		$.tree.plugins.contextmenu.context_menu.object.hide().appendTo("body");
		$("#jstree-contextmenu a")
			.live("click", function (event) {
				if(!$(this).hasClass("disabled")) {
					$.tree.plugins.contextmenu.context_menu.exec.apply(null, [$(this).attr("rel")]);
					$.tree.plugins.contextmenu.context_menu.hide();
				}
				event.stopPropagation();
				event.preventDefault();
				return false;
			})
		$(document).bind("mousedown", function(event) { if($(event.target).parents("#jstree-contextmenu").size() == 0) $.tree.plugins.contextmenu.context_menu.hide(); });
	});
})(jQuery);

Jeśli ktoś chce aby plusik pojawiał się tylko w Operze musi nieco zmodyfikować funkcje onselect i ondeselect dodając stosowny warunek. Przypominam, że jquery umożliwia w łatwy sposób identyfikację przeglądarki, oczywiście jeśli przedstawia się „prawdziwym imieniem”.

Idąc dalej śladem zaspokajania własnych potrzeb napisałem też swój plugin. Nazwałem go zgodnie z przyjętą zasadą „Context conten” i jak się można domyślić służy on do wyświetlania określonej treści powiązanej z danym elementem. W moim konkretnym przypadku miał służyć do wyświetlania podpowiedzi pod elementem w chwili jego wybrania.

(function ($) {
	$.extend($.tree.plugins, {
		"contextcontent" : {
			contenxt_content: {
				class_name: 'jstree-contextcontent',
				show: function(NODE, TREE_OBJ) {
					var id = $(NODE).attr('id');
					var cls = this.class_name;
					$('#'+id+' > .'+cls).show();
					$('#'+id+' > .'+cls+' a').click(function() {
						window.location.href = $(this).attr('href');
					});
				},
				hide : function (NODE, TREE_OBJ) {
					var id = $(NODE).attr('id');
					var cls = this.class_name;
					$('#'+id+' > .'+cls).hide();
				}
			},
 
			callbacks : {
				oninit : function (TREE_OBJ) {
					if (TREE_OBJ.settings.plugins.contextcontent.class_name) {
						$.tree.plugins.contextcontent.contenxt_content.class_name = TREE_OBJ.settings.plugins.contextcontent.class_name;
					}
					var cls = $.tree.plugins.contextcontent.contenxt_content.class_name;
					if(!$.tree.plugins.contextcontent.css) {
 
						var css = '.'+cls+' { display: none;}';
						$.tree.plugins.contextcontent.css = this.add_sheet({ str : css });
					}
				},
				onselect : function (NODE, TREE_OBJ) {
					$.tree.plugins.contextcontent.contenxt_content.show(NODE, TREE_OBJ);
				},
				ondeselect : function(NODE, TREE_OBJ) {
					$.tree.plugins.contextcontent.contenxt_content.hide(NODE, TREE_OBJ);
				}
			}
		}
	});
})(jQuery);

Aby go właściwie użyć trzeba:

1. Dołączyć skrypt do dokumentu html

<script type="text/javascript" src="jquery.js" charset="utf-8"></script>
<script type="text/javascript" src="jsTree/jquery.tree.js"></script>
<script type="text/javascript" src="jsTree/plugins/jquery.tree.contextcontent.js"></script>

2. Dodać do configa jstree stosowny wpis

$(function () {
	$("#demo").tree({
		plugins : {
			contextcontent : {
				class_name: 'jstree-contextcontent'
			}
		}
	});
});

3. No i utworzyć stosowny kod html. Proszę zwrócić uwagę na divy z atrybutem class „jstree-contextcontent”. Nazwę klasy oczywiście można zmienić pod warunkiem, że zrobi się to również w configu.

<div id="demo">
<ul>
	<li id="phtml_1" class="open">
		<a href="#"><ins>&nbsp;</ins>Root node 1</a>
		<div class="jstree-contextcontent">Root node 1 tip...</div>
		<ul>
			<li id="phtml_2">
				<a href="#"><ins>&nbsp;</ins>Child node 1</a>
				<div class="jstree-contextcontent">Child node 1 tip...</div>
			</li>
			<li id="phtml_3"><a href="#">
				<ins>&nbsp;</ins>Child node 2</a>
				<div class="jstree-contextcontent">Child node 2 tip...</div>
			</li>
			<li id="phtml_4">
				<a href="#"><ins>&nbsp;</ins>Some other child node with longer text</a>
				<div class="jstree-contextcontent">Some other child node with longer text tip...</div>
			</li>
		</ul>
	</li>
	<li id="phtml_5"><a href="#">
		<ins>&nbsp;</ins>Root node 2</a>
		<div class="jstree-contextcontent">Root node 1 tip...</div>
	</li>
</ul>
</div>