Namespace’y javascript w widokach cakePHP

Czasem zdarza się tak, że w danym widoku potrzebuję bardzo specyficzną funkcję javascript. Na przykład w widoku invoices/add funkcja count_gros_value, która na podstawie wpisanej wartości w polu netto (net) i stawki var wyliczy wartość brutto (gros).

Załóżmy, że nie wiem jak napisać taką funkcję, żeby była elastyczna, po prostu we wnętrzu mam zaszyte id pól formularza:

function count_gros_value(){
   $("#Invoice[gros]").val( 
      parseFloat($("#Invoice[net]").val()) * 
      parseFloat($("#Invoice[vat]").val())
   );
}
// wywołanie
count_gros_value();

(nie sprawdzałem tego kodu, służy jako przykład i nie mam pojęcia czy dobrze działa).

Oprócz wielkiej nieelastyczności kodu (o czym teraz pisał nie będę) istnieje problem polegający na tym, że nie wiem czy jakiś inny programista nie zdefiniował funkcji count_gros_value gdzieś w layoucie. Oczywiście dostanę error jeśli tak jest, więc poprawię ją na my_count_gros_value() (sic!). Ok, wszystko działa. Jednak przychodzi inny programista i odczuwa potrzebę zdefiniowania funckji my_count_gros_value() w layoucie (no bo count_gros_value() już istnieje, a jego robi coś inaczej/lepiej więc trzeba ją zaimplementować). W ten sposób rozwala twój zaimplementowany fragment i nikt tego nie zauważa.

Jak tego uniknąć? Można zacząć nazywać takie “lokalne” funkcje invoices_index_count_gros_value() ale to zaczyna wyglądać nieestetycznie i aż się ciśnie na usta magiczne słowo “namespace”.

W Javascript nie ma przestrzeni nazw, ale możesz użyć obiektów:

var invoices = {
   index: {
      count_gros_value: function(){
         $("#Invoice[gros]").val( 
            parseFloat($("#Invoice[net]").val()) * 
            parseFloat($("#Invoice[vat]").val())
         );
      }
   }
}
// wywołanie
invoices.index.cunt_gros_value();

to jest już fajniejsze, ale ma dwa problemy:

  1. ciągłe definiowanie obiektów dla namespace’ów jest równie uciążliwe jak nazwa funkcji invoices_index_count…
  2. gdy ktoś przed nami zdefiniuje zmienną var invoices = “invoices”; to mu ją nadpiszemy

Dlatego proponuję taką funckję, która definiuje dowolnie głębokie obiekty udające namespace’y i do tego tworzy je wszytkie w zmiennej o pseudolosowej nazwie (w stylu: Namespace12883495048110.1425782083547854):

function Namespace(namespace_path){
	var w = window;

	window.namespaceFrameworkNamespaceName = 
		window.namespaceFrameworkNamespaceName || 
		"Namespace"+ new Date().getTime() + new Math.random();

	var arr  = (window.namespaceFrameworkNamespaceName + namespace_path).
						split(".");

	for(i in arr){
		w[ arr[i] ] = w[ arr[i] ] || {};
		w = w[ arr[i] ];
	}
	return w;
}

Ok, jakiś uparciuch mógłby się uprzeć, że ktoś może w aplikacji zmiennej ‘namespaceFrameworkNamespaceName’ – takiego problem nie rozwiązywałbym na poziomie kodu, ale personalnym (zwiałbym z projektu) ;)

tak wygląda definiowanie funkcji w namespace:

Namespace("app.users.index").check_form = function(){ 
	document.write("my_check_form()");
};

a tak jej wywołanie:

Namespace("app.users.index").check_form();
// lub gdy mamy zamiar wywołać wiele funkcji z danego namespace:
with(Namespace("app.users.index")){
 check_form();
}

Można by z tego zrobić tez fajne rozszerzenie jQuery,
$().nspc(“app.invoices.index”) (pisałem już o rozszerzaniu jQuery w tutorialu “longPolling”)

Share Button

Czym Ajax jest, a czym nie?

ajax

ajax


Wpadłem ostatnio na dość ciekawą kwestię – błędne pojęcie na temat ajaxa skutkuje czasem błędną (bardziej skomplikowaną niż trzeba) implementacją jakiejś funkcjonalności.

Problem jest następujący: użytkownik wpisuje kod. My sprawdzamy go na serwerze i jeśli jest prawidłowy, to zwracamy pewne powiązane z nim właściwości (np. wysokość rabatu) i pewien komunikat, który musi być wyświetlony na stronie (np. w przypadku błędnego kodu “kod promocyjny jest niepoprawny”. Dodatkowe wymaganie jest takie: wysokość rabatu ma się pojawić koło ceny całkowitej, cena ma się zmniejszyć o ten rabat, a komunikat ma się pojawić nad przyciskiem “Zamów”, które jest w fizycznie w innym miejscu niż cena. Dobrze by było, gdyby w wypadku nieprawidłowego kodu przycisk zamów był nieaktywny.

Jeśli o Ajaxie myślisz tylko jak o sposobie na podmienianie fragmentów strony bez przeładowania to możesz skończyć z takim dziwadłem:

Wysyłasz trzy ajaxy – jeden update’uje jakiś element z rabatem, drugi pole z wiadomością, a trzeci (o zgrozo) przesyła kod html przycisku (raz disabled, raz enabled). No i jeszcze parsujesz, tniesz i sklejasz zwrócony rabat, żeby odjąć go od ceny.

Teraz zacznij myśleć o Ajaxie, który jest sposobem na komunikację z serwerem bez potrzeby przeładowania strony (przeładowanie strony to też komunikacja z serwerem – my pytamy, on odpowiada, o coś prosimy, serwer coś robi). Do tego po otrzymaniu odpowiedzi z serwera możemy z nią zrobić co chcemy – nie musi się automatycznie i bezmyślnie ładować do diva #ajaxResponseToParse ;) I w końcu, jeśli jeszcze nie wiesz, dowiedz się co to znaczy JSON.

Jeśli dodasz te trzy składniki to możesz skończyć z takim rozwiązaniem:

$.getJSON("http://example.com/check_code/XXX", function(data){
   $("#discount-ammount") = data.discount;
   $("#price").html(data.new_price); // *
   $("#message").html(data.message);
   $("#submit").get(0).disabled = data.submit_disabled;
});

(*) myślę, że dobrze takie rzeczy liczyć w jednym miejscu po stronie serwera.

Albo jednym słowem – wyjdź poza wygodne schematyczne myślenie i rozejrzyj się wokół. Możliwe, że wbijasz wkręt obcasem siedząc na młotku i gwoździe mając w kieszeni.

Share Button

Krótka wskazówka – outline obiektowego Javascript w Eclipse

Jeśli używasz Eclipse, piszesz kod Javascript i piszesz go obiektowo może Cię to zainteresować.

Pewnie dobrze wiesz, że jest wiele sposobów na pisanie obiektowego Javascriptu, możesz o różnicach poczytać wszędzie. Jednak moim pragnieniem było, aby eclipse był w stanie pokazać mi outline aktualnie edytowanego pliku js. Po kilku próbach stwierdzam, że działa następujący sposób:

var Klasa = function(){
  this.x = 1;  //definicja pól
}
//definicja metod
Klasa.prototype.setX = function(parametr){
  this.x = parametr;
}
Share Button