Archive for the ‘javascript’ Category

Dodałem do repozytorium druga wersje skryptu sticky buttons, poprawki zawierają:

  • poprawione zachowanie w przypadku przewijania kontenera
  • dodanie rekurencyjnego wyliczania animacji po zjechaniu z guzika
  • animacja po zjechaniu jest tylko „odtwarzana”, klatka po klatce, co powinno przyspieszyć jej przetwarzanie
  • poprawione zachowanie w przypadku złapania guzika
  • poprawione zachowanie w przypadku przejechania po liście guzików (problem z płynnością animacji)

Do tego cała masa zmian w kodzie – można powiedzieć, że skrypt jest napisany od zera. Zawiera ujednolicony system obsługi układu współrzędnych poprzez klasę Point, która zwiera metody manipulujące współrzędnymi x,y odpowiednio. Ona także unifikuje jquery’owe left/right oraz pageX/pageY z eventa.

Dopisałem także funkcyjkę bind służącą wiązaniu parametrów funkcji do późniejszego wykorzystania. W przypadku zakończenia animacji, wysyłane jest zdarzenie, którego obsługa powoduje wygaszenie wątku animacyjnego jak również wykrycia zmiany położenia elementu w przypadku np. scroll’owania zewnętrznego kontenera i zakutalizowania swojej pozycji.

Ten wpis będzie dotyczył zwiekszania czytelności kodu podczas dynamicznego nadpisywana metody w JavaScript’cie.

Problem jest banalny – mamy sobie jakas klase, która oferuje 2 metody, getName/setName i chcielibyśmy w zupełnie innym miejscu dla jednej instancji zamienić implementację setName (poprzednia nas nie interesuje).


function MyClass(){}

MyClass.prototype.getName(){}

MyClass.prototype.setName(){}

Jak widać na powyższym kawałku kodu, przykład jest banalny, teraz utwórzmy sobie instancję MyClass:


// pierwsza instancja
var m1 = new MyClass();

// druga z nadpisana metoda
var m2 = new MyClass();

m2.setName = function newSetName() {}

Na pozór wszystko wydaje się cacy – tworzymy sobie 2 instancje, z których jedna ma troche zmienione zachowanie, wszystko przecież jest czytelne. Owszem, ale co sie stanie jeżeli tworzenie instancji m1 i m2 będzie w znacznie oddalone od miejsca ich uzycia? Wtedy deklaracja MyClass nie da nam żadnej wskazówki, że jej zachowanie może sie zmienić. Jak temu zaradzić – można przesłać metode w konstruktorze, oto pomysł:


//dodajmy parametr i jakies jsdocki

/**
 *@param Function [setName=undefined]
 */
function MyClass(setName) {
   if (setName) {
      this.setName = setName;
   }
}

// reszta bez zmian

Jak widać mamy domyślną implementacje w prototypie a dodatkowo podczas tworzenia klasy, przesyłamy jej funkcję zastepująca setName, wtedy utowrzenie m1 i m2 wyglądałoby tak:


var m1 = new MyClass();

var m2 = new MyClass(function newSetName(){});

Jak widać dodanie parametru do konstruktora, któremu towarzyszy dopisanie krótkiego jsDoc’a zajmuje 5 minut a w zupełności wystarczya za dokumentację.

Latest version numbered 2.79 of Hermes Java File Uploader is out.

This release is caused by problems with Internet Explorer and java6. You can download your free demo at product page

Pewnie niektórzy nie znają, ale pare lat temu pojawiło się bardzo miłe dla oka menu na stronie mjau-mjau, nazwane „sticky buttons”, jednak wykonane w technologii Flash. Ja od jakiegoś czasu rozgryzam jQuery i uznałem, że ciekawym sposobem na podciągnięcie swoich umiejętności, będzie przepisanie takowego menu.

Efekty można zobaczyć na stronie Dominiki Osadców, natomiast źródełko tutaj.

Miłej zabawy.

Trochę upłynęło czasu od ostatniego wpisu, ale byłem dość zajęty pracą na dwoma niezmiernie ciekawymi projektami. Oba są związane ze skryptami ECMA, jeden z nich dotyczy języka JavaScript i jest rozszerzeniem projektu Alladyn w wersji 1.6. Pomysł chodził mi po głowie od dawna, ale dopiero do niedawna dojrzał na tyle, iż uznałem, że czas sie za niego zabrać.

Podstawowe założenia:

  • dodawanie klatek kluczowych poprzez metode addFrames
  • dodawanie klatek oznaczonych labelką
  • wywoływanie animacji „ciągiem” korzystając z metody play
  • wywoływanie animacji korzystając z numeru klatki
  • wywoływanie animacji korzystając z labelki
  • możliwość definowania sztucznej własności, która zawiera callback do skryptu, który ma być wykonany w danej ramce
  • integracja z jQuery
  • definiowanie własności clip korzystając z cztero-elementowej tablicy (clip:rect)
  • dodanie obsługi własności dotyczących kolorów (background-color/border-color/color)
  • obsługa kolorów poprzez trój-elementową tablicę kolejnych składowych RGB
  • dodanie obslugi rownan ruchu Robert’a Penner’a

Wszystkie elementy udało się zrealizować, wynik można obejrzeć tutaj.

Dorzuciłem także parę przykładów:

Zapraszam również do mojego nowszego wpisu, slidemenus oraz strony z demo.

Jeżeli podoba Ci się ten projekt to zapraszam również do obejrzenia, projektu obiektowych komententów skyjs.

Przyglądam się dalej projektowi Rhino i „dopieszczam” system pluginów dostępnych z poziomu języka JavaScript, jednak ostatnio zaskoczyła mnie jedna rzecz. Po konwersji Listy korzystając z metody Context.javaToJS spodziewałem się, że dostęp do elementów będzie możliwy w taki sam sposób jak to jest w przypadku tablicy, czyli np.

mojaLista[0]

.
Niestety ku mojemu rozczarowaniu dostałem tylko wyjątkiem „po oczach” i koniec zabawy. Zacząłem się zastanawiać jak trudne byłoby dodanie takiej funkcjonalności i umożliwienie takich samych „czarów” jak w przypadku np. Expression Language (EL) znanego obytym z JSP.

Porozglądałem się trochę po kodzie i dodanie obsługi listy, wygląda dość prosto, aż tak prosto, że szukam jakiegoś chaczyka (ale o tym za chwilę), dopisałem klasę NativeJavaList, pozamieniałem co trzeba, żeby obsługiwała listę i voila:

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Igor Bukanov
 *   Frank Mitchell
 *   Mike Shaver
 *   Kemal Bayram
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript;

import java.util.List;

/**
 * This class reflects Java lists into the JavaScript environment.
 *
 * @author Marek Będkowski
 * @see NativeJavaClass
 * @see NativeJavaObject
 * @see NativeJavaPackage
 * @see NativeJavaArray
 */

public class NativeJavaList extends NativeJavaObject
{
    static final long serialVersionUID = -924022554283675333L;

    @Override
    public String getClassName() {
        return "JavaList";
    }

    public static NativeJavaList wrap(Scriptable scope, Object list) {
        return new NativeJavaList(scope, list);
    }

    @Override
    public Object unwrap() {
        return list;
    }

    public NativeJavaList(Scriptable scope, Object list) {
        super(scope, list, ScriptRuntime.ObjectClass);
        if( !(list instanceof java.util.List)){
            throw new RuntimeException("java.util.List expected");
        }
        this.list = (List<Object>)list;
        this.cls = list.getClass().getComponentType();
    }

    @Override
    public boolean has(String id, Scriptable start) {
        return id.equals("length") || super.has(id, start);
    }

    @Override
    public boolean has(int index, Scriptable start) {
        return 0 <= index && index < list.size();
    }

    @Override
    public Object get(String id, Scriptable start) {
        if (id.equals("length"))
            return new Integer(list.size());
        Object result = super.get(id, start);
        if (result == NOT_FOUND &&
            !ScriptableObject.hasProperty(getPrototype(), id))
        {
            throw Context.reportRuntimeError2(
                "msg.java.member.not.found", list.getClass().getName(), id);
        }
        return result;
    }

    @Override
    public Object get(int index, Scriptable start) {
        if (0 <= index && index < list.size()) {
            Context cx = Context.getContext();
            Object obj = list.get(index);
            return cx.getWrapFactory().wrap(cx, this, obj, cls);
        }
        return Undefined.instance;
    }

    @Override
    public void put(String id, Scriptable start, Object value) {
        // Ignore assignments to "length"--it's readonly.
    	// also make sure that nobody overrides list's interface
        if (!id.equals("length") || super.get(id, start) != null) {
            throw Context.reportRuntimeError1(
                "msg.property.or.method.not.accessible", id);
        }
    }

    @Override
    public void put(int index, Scriptable start, Object value) {
        if (0 <= index && index < list.size()) {
        	list.set(index, Context.jsToJava(value, cls));
        }
        else {
            throw Context.reportRuntimeError2(
                "msg.java.array.index.out.of.bounds", String.valueOf(index),
                String.valueOf(list.size() - 1));
        }
    }

    @Override
    public Object getDefaultValue(Class<?> hint) {
        if (hint == null || hint == ScriptRuntime.StringClass)
            return list.toString();
        if (hint == ScriptRuntime.BooleanClass)
            return Boolean.TRUE;
        if (hint == ScriptRuntime.NumberClass)
            return ScriptRuntime.NaNobj;
        return this;
    }

    @Override
    public Object[] getIds() {
        Object[] result = new Object[list.size()];
        int i = list.size();
        while (--i >= 0)
            result[i] = new Integer(i);
        return result;
    }

    @Override
    public boolean hasInstance(Scriptable value) {
        if (!(value instanceof Wrapper))
            return false;
        Object instance = ((Wrapper)value).unwrap();
        return cls.isInstance(instance);
    }

    @Override
    public Scriptable getPrototype() {
        if (prototype == null) {
            prototype =
                ScriptableObject.getClassPrototype(this.getParentScope(),
                                                   "Array");
        }
        return prototype;
    }

    List<Object> list;
    Class<?> cls;
}

Później „uświadomienie” obiektowi WrapFactory, że już obsługa listy jest i już można szaleć…

else if( obj instanceof java.util.List ){
        	return NativeJavaList.wrap(scope, obj);
        }

Jednak jak wspominałem na początku, sprawa wygląda podejrzanie prosto, że aż nasuwa się pytanie, dlaczego do tej pory nikt z teamu Rhino się tym nie zajął? Uważna lektura kodu, powyżej może podsunąć jedną z hipotez. Otóż typizacja kolekcji w Javie działa tylko podczas kompilacji, w czasie wykonywania informacja o typie jest wymazywana, w celu kompatybilności wstecznej. Dodając do tego, że JavaScript jest dynamicznie typowany mamy prośbę o kłopoty.

Lista może otrzymać ze strony JS’a, dowolny obiekt (java.lang.Object), żadnego błedu, ostrzeżenia nic – dopiero w momencie gdy będziemy próbowali pobrać naszego Inta, z tablicy Stringów dostaniemy „po łapkach”. Jak to mówią „nie ma nic za darmo”, ale moim zdaniem ułatwienie jest na tyle przyjemne, że postanowiłem pójść dalej tym tropem i obsłużyć jeszcze Map’y, oto wynik:

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Igor Bukanov
 *   Frank Mitchell
 *   Mike Shaver
 *   Kemal Bayram
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript;

import java.util.Iterator;
import java.util.Map;

/**
 * This class reflects Java Maps into the JavaScript environment.
 *
 * @author Marek Będkowski
 * @see NativeJavaClass
 * @see NativeJavaObject
 * @see NativeJavaPackage
 */

public class NativeJavaMap extends NativeJavaObject
{
    static final long serialVersionUID = -924022554283675333L;

    @Override
    public String getClassName() {
        return "JavaMap";
    }

    public static NativeJavaMap wrap(Scriptable scope, Object map) {
        return new NativeJavaMap(scope, map);
    }

    @Override
    public Object unwrap() {
        return map;
    }

    public NativeJavaMap(Scriptable scope, Object map) {
        super(scope, map, ScriptRuntime.ObjectClass);
        if( !(map instanceof java.util.Map)){
            throw new RuntimeException("java.util.Map expected");
        }
        this.map = (Map<Object,Object>)map;
        this.cls = this.map.keySet().toArray().getClass().getComponentType();
    }

    @Override
    public boolean has(String id, Scriptable start) {
        return id.equals("length") || super.has(id, start) || map.containsKey(id);
    }

    @Override
    public boolean has(int index, Scriptable start) {
        return map.containsKey(index);
    }

    @Override
    public Object get(String id, Scriptable start) {
        if (id.equals("length"))
            return new Integer(map.size());
        Object result = super.get(id, start);
        // instead of throwing error immediately try searching id as a map key
        if (result == NOT_FOUND &&
            !ScriptableObject.hasProperty(getPrototype(), id))
        {
        	if( !map.containsKey(id) ) {
	            throw Context.reportRuntimeError2(
	                "msg.java.member.not.found", map.getClass().getName(), id);
        	}
            Context cx = Context.getContext();
        	Object obj = map.get(id);
            return cx.getWrapFactory().wrap(cx, this, obj, cls);
        }
        return result;
    }

    @Override
    public Object get(int index, Scriptable start) {
        if (map.containsKey(index)) {
            Context cx = Context.getContext();
            Object obj = map.get(index);
            return cx.getWrapFactory().wrap(cx, this, obj, cls);
        }
        return Undefined.instance;
    }

    @Override
    public void put(String id, Scriptable start, Object value) {
    	// also make sure that nobody overrides list's interface
        if (super.get(id, start) != null) {
            throw Context.reportRuntimeError1(
                "msg.property.or.method.not.accessible", id);
        }
       	map.put(id, Context.jsToJava(value, cls));
    }

    @Override
    public void put(int index, Scriptable start, Object value) {
       	map.put(index, Context.jsToJava(value, cls));
    }

    @Override
    public Object getDefaultValue(Class<?> hint) {
        if (hint == null || hint == ScriptRuntime.StringClass)
            return map.toString();
        if (hint == ScriptRuntime.BooleanClass)
            return Boolean.TRUE;
        if (hint == ScriptRuntime.NumberClass)
            return ScriptRuntime.NaNobj;
        return this;
    }

    @Override
    public Object[] getIds() {
        Object[] result = new Object[map.size()];
        Iterator<Object> iter = map.keySet().iterator();
        int i = 0;
        while(iter.hasNext()) result[i++] = iter.next();
        return result;
    }

    @Override
    public boolean hasInstance(Scriptable value) {
        if (!(value instanceof Wrapper))
            return false;
        Object instance = ((Wrapper)value).unwrap();
        return cls.isInstance(instance);
    }

    @Override
    public Scriptable getPrototype() {
        if (prototype == null) {
            prototype = ScriptableObject.getClassPrototype(this.getParentScope(),"Object");
        }
        return prototype;
    }

    Map<Object,Object> map;
    Class<?> cls;
}

Tutaj jeszcze słowo wyjaśnienia – żeby komuś nie przyszło do głowy nadpisanie interfejsu Javowego jakimiś głupotami, podczas wstawiania elementów najpierw sprawdzam czy czasem własność o podanej nazwie już nie istnieje i dopiero dopisuję ją do listy. Możnaby się oczywiście pokusić o sprawdzanie, dodawanie gdzieś na boku, udostępnianie przez getIds i odyczt, ale to zostawię już jako ćwiczenie dla czytelnika.

Hi

I found out about rhino some weeks ago and I started looking for some reasonable way to improve my tests by adding support for XmlHttpRequests and I got to this project.

What I found out missing/problematic is support for HTML document parsing, so my idea is to use JTidy but since org.w3c.tidy.Tidy doesn’t support javax.xml.parsers.DocumentBuilder interface I added a small Tidy adapter which extends DocumentBuilder and can be used directly in env.rhino.js.

Here’s short implementation of this idea:

package pl.bedkowski.p.java.tidy;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;

import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.tidy.Tidy;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class TidyDocumentBuilder extends DocumentBuilder {

	private Tidy tidy;
	private OutputStream out = new OutputStream() {
		@Override
		public void write(int b) throws IOException {
			// do nothing - this is for getting just the DOMDocument
		}
	};
	private PrintStream err = new PrintStream(out);

	public TidyDocumentBuilder(Tidy tidy){
		this.tidy = tidy;
	}

	public TidyDocumentBuilder(){
		this(new Tidy());
		tidy.setMakeClean(false);
		tidy.setQuiet(true);
		tidy.setErrout(new PrintWriter(err));

	}
	@Override
	public Document newDocument() {
		return Tidy.createEmptyDocument();
	}

	@Override
	public Document parse(InputSource inputSource) throws SAXException, IOException {
		InputStream in = inputSource.getByteStream();
		if( in == null) {
			in = new FileInputStream(inputSource.getSystemId());
		}
		return tidy.parseDOM(in, out);
	}

	@Override
	public void setEntityResolver(EntityResolver resolver) {}
	@Override
	public void setErrorHandler(ErrorHandler errorHandler) {}
	@Override
	public DOMImplementation getDOMImplementation() {return null;}
	@Override
	public boolean isNamespaceAware() {return false;}
	@Override
	public boolean isValidating() {return false;}

}

As you can see there are 2 things to improve:
- it lies in my project’s namespace, which might be removed if Tidy extended DocumentBuilder
- there are still some „blanks” left but it’s already usable in env.js

And than just replace calls in env_rhino.js (line #343)

var htmlParser;
if( !useTidy ) {
    var htmlDocBuilder = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance();
    htmlDocBuilder.setNamespaceAware(false);
    htmlDocBuilder.setValidating(false);
    htmlParser = htmlDocBuilder.newDocumentBuilder();
}
else {
    htmlParser = new Packages.pl.bedkowski.p.java.tidy.TidyDocumentBuilder();
}
    $env.parseHTML = function(htmlstring){
        return htmlParser.parse(
                  new java.io.ByteArrayInputStream(
                        (new java.lang.String(htmlstring)).getBytes("UTF8")))+"";
    };

    var xmlDocBuilder = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance();
    xmlDocBuilder.setNamespaceAware(true);
    xmlDocBuilder.setValidating(true);

    $env.parseXML = function(xmlstring){
        return xmlDocBuilder.newDocumentBuilder().parse(
                  new java.io.ByteArrayInputStream(
                        (new java.lang.String(xmlstring)).getBytes("UTF8")))+"";
    };

Ostatnio przegladajac informacje nt. przyszlosci i kierunkow rozwoju projektu eclipse natknalem sie na informację, że planowane są spore zmiany w systemie pluginów, z których dla mnie najważniejsze na chwilę obecną były:
- systemu okienek w oparciu o XWT (XUL/SWT)
- interfejsu pluginow dostepnego z poziomu języka JavaScript (JS)

Z oboma technologiami miałem już do czynienia, chociażby podczas budowy mojego pluginu do popularnej przeglądarki Firefox. Jednak od razu pojawiło się pytanie – w jaki sposób programiści eclipse’a będą udostępniać interfejsy w JS’ie? Odpowiedź na to pytanie jest już znana od lat – istnieje bowiem biblioteka, będą interpreterem i kompilatorem języka JavaScript w całości napisana w Javie, nazywa się rhino.

Dokumentacja projektu jest dość dobra i pozwoliła mi w ciągu paru godzin ogarnąć możliwości tego rozwiązania, no i narodził się pomysł, żeby w jednym z projektów udostępnić systemowi pluginów także api JavaScript’owe, co mogłoby znacząco wpłynąc na szybkość ich powstawania, a także wykorzystać możliwości np. operatorów czy warunków.

Obecny system pluginów opiera się o 2 interfejsy w Javie i abstrakcyjną klasę bazową, która udostępnia 2 metody, więc dobrze by było, żeby z poziomu JavaScriptu także był do nich dostęp. Nie jest to takie trudne, trzeba tylko, dodać klasie bazowej funkcjonalność obiektu Scriptable, co pozwoli go wykorzystać jako kontekst danego skryptu i umożliwić wywołanie wspomnianych metod z poziomu skryptu.

Jeżeli metody są statyczne to sprawa jest banalnie prosta, bo wtedy można je dodać jako funkcje i wogóle nie trzeba się wysilać:)

Kod poniżej odpowiada za przykładową implementację.

Interfejs:

public interface MyPlugin{
public void executeMyPlugin();
}

Klasa bazowa:

public abstract class MyBaseClass extends ScriptableObject{
public void doSomething(){
}
public void doSomething2(){
}
public static void executePlugins(List<String> pluginsList){
// stwórz kontekst
Context cx = Context.enter();
MyBaseClass executePlugins = new MyBaseClass();
ScriptableObject scope = (ScriptableObject) cx.initStandardObjects(executePlugins);
// dla każdego pliku
// wczytaj plik z dysku i zapisz go w String'u code
String code = "";
// dodaj własności dostępne jako globale z punktu widzenia skryptu
String[] scriptAvailableFunctions = { "doSomething", "doSomething2" };
scope.defineFunctionProperties(scriptAvailableFunctions, MyBaseClass.class, ScriptableObject.DONTENUM);

// dodaj potrzebne importy
String s = "var context = JavaImporter();\n" +
"context.importClass(Packages.MyPlugin);\n" +
"with (context) {\n " + code + "; p = new MyPlugin({executeMyPlugin:executeMyPlugin});p.executeMyPlugin();\n\n}" + "";

// wykonaj skrypt
cx.evaluateString(scope, scode, "MyScript", 0, null);
}
}

Plugin1.js

function executeMyPlugin(){
  out.println("Hello from plugin1!")
}

Jak widać mój kontekst dołącza do pluginu potrzebne importy, czyli wszystko co jest dostępne w pakietach aplikacji, tak żeby żaden z pluginów nie musiał tego robić sam i wszystko wygląda dobrze, do czasu gdy któryś z nich potrzebuje wykonać jakąś metodę np. z pakietu java.io.

Wtedy można:
- zezwolić mu na samodzielne wykonywanie importu
- stworzyć dodatkowy opis np. w XML’u, który poza ciałem pluginu, mógły zawierać także dodatkowe informacje m.in. o zależnośćiach

Przykładowy XML, mogłby wyglądać tak:

<plugin name="Plugin1" id="Plugin1">
  <dependencies>
    <packages>
      <list>
	java.io;
	java.lang;
      </list>
    </packages>
    <classes>
      <list>
	java.io.File
      </list>
    </clases>
  </dependencies>
  <interfacemethod>
    <[[CDATA
      function executeMyPlugin(){
	out.println("Hello from plugin1!");
      }
    ]]>
  </interfacemethod>
</plugin>

I już jest znacznie ładniej, bo poza samym kodem mamy jeszcze trochę metadanych potrzebnych do wykonania pluginu.

Wyobraźmy sobie teraz sytuację, że chcemy w ten sam sposób dodać pluginy korzystające z różnych interfejsów, wtedy jedyne co wystarczy dodać atrybut interface, próbujemy:

<plugin name="Plugin1" id="Plugin1" interface="MyPlugin">
<!-- reszta tak jak byla -->
</plugin>

Co to daje – deklaratywną kontrolę tego co faktycznie zostaje wykonane, do tego możnaby się pokusić o sprawdzenie jakie metody są w interfejsie i sprawdzenie czy aby napewno kod JavaScript’owy zawiera ich deklarację, co w połączeniu z założeniem, że w przyszłości pojawi się do tego jakiś eleganckie gui (np. korzystająć z edytora eclipse’owego w połączeniu z czarodziejem), może dać naprawdę świetne rezultaty.

Pluginy tak naprawdę są elementem pochodnym, mogą być wykorzystane, do wykonywania akcji przewidzianych dla jakichś danych z zwenątrz. Zazwyczaj to jaki plugin ma być wykonany też trzeba w jakiś sposób zadeklarować. Posłużymy się tutaj znowu XML’em:

<map>
<element from="element1" to="element2" plugin="Plugin1" />
</map>

Tutaj mamy prostą postać pluginu, masz element i na nim wykonaj dana operację. Jednak co by było gdybyśmy chcieli stworzyć nowy element na podstawie dwóch innych elementów, trzebaby podać pluginowi skąd ma pobrać wartości, np w ten sposób:

<map>
  <element to="element2">
    <from plugin="Plugin1" separator=";">
      <el>key1</el>
      <el>key2</el>
    </from>
  </element>
</map>

Teraz widać, że plugin będzie miał dostęp do dodatkowych elementów opisywanych przez klucze key1/key2, a wynikiem jego działania ma być połączony string. Idąc dalej tym tropem może się okazać, że przyjdzie potrzeba wykonania na danych operacji matematycznych, np. dodania wartości 2 pól.

<map>
  <element to="element2">
    <from plugin="Plugin1" operation="a+b">
      <el name="a">key1</el>
      <el name="b">key2</el>
    </from>
  </element>
</map>

Jedyna różnica w stosunku do poprzedniego przykładu polega na tym, że wartości pól muszą być zsumowane, aby to zrealizować przy użyciu JS’a, możemy zrobić tak:


var obj = {};
for(el in elements) {
  obj[el.getName()] = inputMap.get(el.value())
}

with(obj){
eval('res='+el.getParentNode().getOperation())
}

out.println("Result of operation is: "+res)

I prosze, dynamiczna typizacja załatwi resztę! Jedyne o co się trzeba zatroszczyć to przechwycenie ew. błędów.

Ostanio miałem okazję zmierzyć sie z rozszerzeniem do popularnej przeglądarki Firefox, które miało umożliwiać wykonywanie testów Unitowych w JavaScript’cie. Jak zwykle nad zaletami testów ani wprowadzenia do xula robić nie będę, bo po co :) Chciałem wspomnieć o problemie z jakim przyszło mi się zmierzyć.

Otóż plugin ma umożliwiać uruchamianie testów napisanych w JavaScript’cie, a jak wiadomo jest on obsługiwany przez przeglądarke, więc trzeba było znaleźć sposób, żeby można było nieinwazyjnie z dokumentu html pobrać zadeklarowane testy, uruchmić je jeden po drugim dostarczając jednocześnie elegancki pasek postępu użytkownikowi. Niby proste, bo w końcu pasek postępu mam gdzieś już zrobiony, w samym JS’ie też jakiś początkujący nie jestem, przygoda z GTK2+/GTKmm, pogłębiła wiedzę nt budowania interfejsu i ogólnie w ciemię bity nie jestem, więc powinno pójść gładko.

Troche szukania i znalazłem to czego mi trzeba, XUL udostępnia 2 bardzo przyjemne obiekty: iframe i browser, ale do pełni szczęścia, trzeba dać możliwość aktualizowania już załadowanych testów przed wykonaniem. No niby proste, na obiekcie webNavigation, wywołujemy metodę reload, przypisujemy zdarzenie onload do obiektu contentDocument, jak opisuje to dokumentacja i nie działa :D

W sumie to dałbym sobie spokój, ale wrzuciłem mój kawałek kodu na stronę z rozszerzeniami FF, no i zgłosił się drugi taki jak ja, któremu tego brakuje, więc trzeba było przysiąć trochę i poszukać, jak to obejść.

Już już miałem się poddać, gdy znalazłem dyskusję na forach mozilli opisującym ten sam problem, wraz z rozwiązaniem, a jest ono banalne. Zamiast czytać dokumentację, trzeba po staremu dopisać sobie zdarzenie do firame’a i tak czekać spokojnie aż do nas przwędruje.

Poprawka gotowa, użytkownik/klient zadowolony, więc można znowu zasnąc snem sprawiedliwego:)

Na jednym forum pojawiło się pytanie o to jak zrobić przejście między obrazkami no i od razu pomyślałem o Alladynie, a przy okazji chciałem sprawdzić na ile tak naprawdę ułatwia on pracę i nie zawiodłem się – zrobienie animacji zajeło niecałe pół godzinki i poza samym efektem fade, dodałem jeszcze rozwijanie/zwijanie jako dodatkowy bajerek. Gdyby api było bardziej przyjazne niż 3 literowe skróty poszłoby jeszcze szybciej. No cóż trzeba się bedzie tym zająć :)

Efekt tutaj

Wersja druga