[JavaScript] Prototype Erweiterungen + Ajax.Upload

Dieses Thema im Forum "Webentwicklung" wurde erstellt von Murdoc, 24. Juli 2011 .

?

Findet Ihr diese Erweiterungen nützlich?

  1. Ja, nutze das Script auch in meinen Projekt(en)

    3 Stimme(n)
    30,0%
  2. Ja, nutze das Script aber nicht sondern schau mir nur Sachen daraus ab

    1 Stimme(n)
    10,0%
  3. Nützlich ja, nutze aber ein anderes Framework

    4 Stimme(n)
    40,0%
  4. Javascript? Ich programmiere nur mit jQuery (PLONK)

    2 Stimme(n)
    20,0%
  5. Nein

    0 Stimme(n)
    0,0%
  1. 24. Juli 2011
    Prototype Erweiterungen + Ajax.Upload

    Ich hab eine Sammlung an Funktionen und Erweiterungen für Prototype erstellt, die ich die letzten Jahre immer wieder eingesetzt hab und nützlich finde.

    Folgendes wurde hinzugefügt:

    dom:loaded Support für die Funktion $()

    Man kann nun über die Funktion $() direkt einen Callback für den Event "dom:loaded" hinzufügen.

    Deklaration:
    Code:
    $(function callback) -> Event.Handler
    Beispiel:
    Code:
    $(function() { ... });
    Führt die Funktion im dom:loaded event aus.

    vergleichbar mit:
    Code:
    document.observe("dom:loaded", function() { ... });
    ----

    HTML Interpretation

    Die Funktion $() kann nun HTML interpretieren und gibt die erstellten Elemente zurück.

    Deklaration:
    Code:
    $(string html) -> Element
    Beispiel:
    Code:
    var em = $('<em>hallo welt</em>');
    interpretiert den übergebenen string als html-element und gib dieses zurück

    vergleichbar mit:
    Code:
    var em = new Element('em').insert('hallo welt');
    Info: leere tags sind auch möglich:
    Code:
    var em = $('<em />');
    Analog dazu ist es auch möglich Elemente zu erstellen:
    Code:
    var em = $('em', {});
    ----

    Elemente direkt an document.body anhängen

    Deklaration:
    Code:
    $.insert(string content) -> void
    $.insert(Element element) -> void
    Beispiel:
    Code:
    $.insert('<em>hallo welt</em>');
    Fügt <em>hallo welt</em> an das Ende von <body> hinzu. Sollte der Quelltext beim Aufruf der Funktion noch nicht komplett geladen sein, werden alle Elemente in einem Array gesammelt und erst bei dom:loaded hinzugefügt.

    vergleichbar mit:
    Code:
    $(document.body).insert('<em>hallo welt</em>');
    ----

    Skripte nachladen include/require

    Deklaration:
    Code:
    $.include(string src) -> void
    $.require(string src) -> void
    Code:
    $.include('beispiel.js');
    läd das "beispiel.js" nach

    Code:
    $.require('beispiel.js');
    läd ebenfalls "beispiel.js" nach, prüft aber zuvor ob es schon nachgeladen wurde

    ----

    Die bekannte Funktionalität bleibt erhalten.
    Code:
    var foo = $("foo"); // element mit der id "foo"
    var bar = $("foo", "bar"); // elemente mit der id "foo" und "bar"
    ----

    $$ gibt nun einen Wrapper zurück anstelle einer NodeList

    Es ist nun möglich ohne den Umweg über .invoke(...) Methoden der gefundenen Elemente auszuführen.

    Ohne Erweiterung:
    Code:
    $$('a.alert').invoke('observe', 'click', function(event) {
     event.stop();
     alert('geklickt!');
    });
    
    $$('div.hide-me').invoke('hide');
    Mit Erweiterung:
    Code:
    $$('a.alert').observe('click', function(event) {
     event.stop();
     alert('geklickt!');
    });
    
    $$('div.hide-me').hide();
    Achtung: Damit lassen sich nur Methoden die in Element.Methods definiert wurden aufrufen.

    $$("...").getAttribute() z.b. geht nicht! macht auch keinen sinn, weil keine werte zurückgegeben werden können.

    Bitte nicht verwechseln mit $$.first().

    ----

    $$.first Erweiterung

    Mit dieser Funktion ist es möglich nur den ersten Fund eines CSS-Selektors zu ermitteln.

    Deklaration:
    Code:
    $$.first(string selector) -> Element
    Beispiel:
    Code:
    var div = $$.first('div.content');
    sucht nach allen div's mit der Klasse "content" und gibt den ersten Fund zurück

    vergleichbar mit:
    Code:
    var div = $$('div.content')[0];
    ----

    Ajax.Upload Klasse

    Mit dieser Klasse ist es möglich Dateien über XMLHttpRequest2 hochzuladen.
    Im Grunde entspricht diese Klasse der Altbekannten Ajax.Request Klasse, führt aber zwei neue Callbacks ein:

    - onProgress()
    Wird gefeuert wenn eine Datei hochgeladen wird und es Daten zu verarbeiten gibt.

    - onUploaded()
    Wird gefeuert wenn der Upload abgeschlossen wurde.

    Beispiel:
    Code:
    var input = $$.first('#form input[type="file"]');
    
    new Ajax.Upload("upload.php", {
     method: "POST",
     upload: input.files[0],
    
     onProgress: function(data) {
     console.log("Fortschritt: " + ((data.loaded / data.total) * 100) + " %");
     },
    
     onUploaded: function() {
     console.log("Upload komplett");
     },
    
     onSuccess: function(res) {
     console.log("Abfrage erledigt");
     }
    });
    Ob man diese Funktion vorhanden ist kann man mittels der Eigenschaft
    Ajax.UPLOAD_SUPPORTED prüfen.

    Bzw.:
    Prototype.BrowserFeatures.XMLHttpRequest2
    Prototype.BrowserFeatures.FileAPI

    ----

    download:

    quelltext http://murdoc.eu/protoext/protoext.js
    minified: http://murdoc.eu/protoext/protoext.min.js
    minified + gzip: http://murdoc.eu/protoext/protoext.min.js.gz


    Weitere Vorschläge immer gern gesehen!

    Viel Spaß
     
  2. 13. August 2011
    AW: Prototype: Erweiterung der Funktionen $ und $$

    kleines update:

    - $$() gibt nun einen wrapper zurück anstelle einer nodelist.
    - Ajax.Upload support

    weiters dazu hab ich im startpost aktualisiert.
     
  3. 15. August 2011
    AW: Prototype: Erweiterung der Funktionen $ und $$

    Da keiner irgendwie Feedback gab mal ne Frage an euch:
    Interessiert das eigl. überhaupt jemanden?

    Oder denkt Ihr euch sowas wie "Jaja, der will doch nur Angeben pfft."

    Also nutzt jemand das Script oder blockier ich damit nur die erste Seite der Webmaster Forums? ^^

    Geplant war im übrigen noch Support für Websockets, Server Events und andere neue Sachen die bisher in Prototype noch fehlen, da seit nem guten Jahr kein Update mehr kam - und bis Prototype2 wird vermutlich auch kein neues Minor-Release mehr kommen.
     
  4. 15. August 2011
    AW: Prototype Erweiterungen + Ajax.Upload

    Interesse: Ja.

    Hab den Thread eben erst bemerkt. Liest sich sehr gut alles.Aber um es wirklich zu bewerten, muss man es halt testen. Werde das die Woche versuchen.
     
  5. 18. August 2011
    AW: Prototype Erweiterungen + Ajax.Upload

    Habe nun alles getestet.

    Funktioniert alles wie versprochen. Besonders der Ajax Upload ist elegant gelöst. Gefällt mir sehr gut. Besonders onProcess ist nützlich.

    Tolle Arbeit.
     
  6. 7. Oktober 2011
    AW: Prototype Erweiterungen + Ajax.Upload

    arbeite derzeit an einem form-validator mit benutzerdefinierten fehlermeldungen (HTML5 + fallback).

    Spoiler
    Code:
    Form.Validator = Class.create((function() {
     
     var USE_NATIVE_VALIDATOR = (function() {
     var form = document.createElement('form'),
     check = (typeof form.checkValidity == 'function');
     
     form = null;
     return check;
     })();
     
     var NO_PATTERN_ELEMENTS = ['select', 'check', 'radio', 'submit', 'button', 'image'],
     FORM_ELEMENT_SELECTOR = 'input:not([type="button"]):not([type="image"]):not([type="submit"]), select, textarea';
     
     /**
     * constructor
     *
     */
     function initialize(form, options) {
     this.form = form;
     
     // just references
     this.types = Form.Validator.Types;
     this.flags = Form.Validator.Flags;
     this.cntrs = Form.Validator.Constraints;
     
     this.options = Object.extend({
     // called when a field is invalid
     onInvalid: function() {},
     // called when validation is done and the form seems to be invalid
     onFailure: function() {},
     // called when the form seems to be valid
     onSuccess: function(form) { form.submit(); }
     }, options || {});
     
     if (USE_NATIVE_VALIDATOR) {
     // prepares the form and let the browser validate its values
     this.prepare();
     } else {
     // validates the the form on submit
     this.form.on("submit", this.validate.bind(this));
     }
     } 
     
     // private
     function _type(field) {
     var type = field.getAttribute('type');
     
     if (!type) {
     type = field.nodeName.toLowerCase();
     if (type === 'textarea')
     type = 'text';
     }
     
     switch (type) {
     case 'url':
     case 'email':
     case 'tel':
     case 'search':
     case 'password':
     case 'number':
     return 'text';
     
     case 'checkbox':
     return 'check';
     }
     
     return type;
     }
     
     function _value(field) {
     switch (_type(field)) {
     case 'text':
     return field.value.empty() ? false : field.value;
     
     case 'radio':
     case 'check':
     return field.checked;
     
     case 'select':
     if (field.selectedIndex === -1)
     return false;
     
     return field.options[field.selectedIndex].value;
     }
     
     return false;
     }
     
     /**
     * checks a field
     *
     * @param Element field
     * @param String type
     * @return Boolean
     */
     function check(field, type) {
     // grab pattern and test it
     return new RegExp(field.getAttribute("pattern") || this.types.get(type)).test(field.value);
     }
     
     /**
     * fetches an error-message
     *
     * @param Element field
     * @param String type
     * @param String format
     * @return String|undefined
     */
     function notice(field, type, format) {
     var message = this.cntrs.get(type)[format] || "undefined";
     
     if (USE_NATIVE_VALIDATOR)
     return message;
     
     // display an error-bubble :-)
     if (this.errors === 1)
     console.log("first error", field);
     
     console.log(message);
     }
     
     /**
     * prepares the form to be validated by the browser
     *
     * @void
     */
     function prepare() {
     // handle first-invalid event
     var handler = (function(event) {
     this.form.removeEventListener("invalid", handler, true);
     this.options.onFailure(this.form);
     }).bind(this);
     
     // handle invalid event for elements
     var invalid = (function(event) {
     var element = event.element();
     
     // handle element
     var name = element.nodeName.toLowerCase(),
     type = _type(element);
     
     var error;
     
     if (element.hasAttribute("required") && _value(element) === false) {
     // looks like "required" triggered this error
     error = 'required';
     } else if (NO_PATTERN_ELEMENTS.indexOf(type) == -1) {
     // must be an input element
     error = element.getAttribute("type");
     } else {
     // remove custom message
     element.setCustomValidity("");
     event.preventDefault();
     return; // done
     }
     
     ++this.errors;
     this.options.onInvalid(element, error, this.form);
     
     // add message
     element.setCustomValidity(this.notice(element, error, type));
     }).bind(this);
     
     this.form.addEventListener("invalid", handler, true);
     this.form.addEventListener("invalid", invalid, true);
     
     // submit -> valid
     this.form.observe("submit", function(event) { 
     event.stop();
     this.options.onSuccess();
     }.bind(this));
     }
     
     /**
     * validates the form
     *
     * @param Event event
     * @void
     */
     function validate(event) {
     event.stop();
     
     var trigger = event.element(), radios = [];
     
     if (trigger && ["input", "button"].indexOf(trigger.nodeName.toLowerCase())
     && trigger.hasAttribute("formnovalidate")) {
     this.options.onSuccess(this.form);
     return; // done
     }
     
     trigger = null;
     
     this.errors = 0;
     
     this.form.select(FORM_ELEMENT_SELECTOR).each(function(field) { 
     // fetch internal-type
     var internal_type = _type(field);
     
     // handle flags
     this.flags.each(function(flag) {
     // special case: radio
     if (internal_type === 'radio') {
     var name = field.getAttribute('name');
     if (radios.indexOf(name) > -1)
     return;
     
     radios.push(name);
     }
     
     if (field.hasAttribute(flag.key) && !flag.value(field, this.form)) {
     ++this.errors;
     this.notice(field, flag.key, internal_type, this.form);
     this.options.onInvalid(field, flag.key, this.form);
     throw $break;
     }
     }, this);
     
     // select-, check-, radio-fields and textareas are done at this point
     if (field.nodeName.toLowerCase() === 'textarea'
     || NO_PATTERN_ELEMENTS.indexOf(internal_type) > -1)
     return;
     
     // handle field-specific type/pattern attributes
     var type = field.getAttribute('type');
     
     if (!this.check(field, type)) {
     ++this.errors;
     this.notice(field, type, internal_type, this.form);
     this.options.onInvalid(field, type, this.form);
     throw $break;
     }
     }, this);
     
     // submitable?
     if (this.errors !== 0) {
     this.options.onFailure(this.form);
     return this;
     }
     
     this.options.onSuccess(this.form);
     return this;
     }
     
     return {
     initialize: initialize,
     validate: validate,
     check: check,
     notice: notice,
     prepare: prepare
     };
    })());
    
    // predefined types 
    Form.Validator.Types = $H({
     email: /^\w+@(?:(?:\w+\.)+[a-z]{2,}|\w+)$/i,
     tel: /^[\d\/\.\-\s]+$/,
     url: /^[a-z]+[a-z0-9\-]*:\/\/(?:\w+(?::\w+)?@)?(?:(?:\w+\.)+[a-z]{2,}|\w+)(?::[0-9]+)?(?:\/[^?#]*(?:\?[^#]*)?(?:#.*)?)?$/i
    });
    
    // predefined constraits
    Form.Validator.Constraints = $H({
     email: { text: 'Bitte geben Sie eine korrekte E-Mail an.' },
     tel: { text: 'Bitte geben Sie eine Telefonnummer an.' },
     url: { text: 'Bitte geben Sie eine korrekte URL an.' },
     
     required: { 
     text: 'Bitte füllen Sie dieses Feld aus.',
     check: 'Bitte Klicken Sie dieses Feld an.',
     radio: 'Bitte Klicken Sie eines der Felder an.',
     select: 'Bitte wählen Sie eine Option aus.'
     },
     
     // maxlength is silent
     
     // todo: add messages
     date: { },
     month: { },
     week: { },
     range: { },
     number: { },
     time: { },
     datetime: { },
     "datetime-local": { }
    });
    
    // predefined flags
    Form.Validator.Flags = $H({
     // todo: add validators for min and max
     
     maxlength: function(element, form) {
     var len = element.value.length,
     max = parseInt(element.getAttribute("maxlength"));
     
     if (len > max)
     element.value = element.value.substr(0, max);
     
     return true;
     },
     
     required: function(element, form) { 
     switch (element.nodeName.toLowerCase()) {
     case 'input':
     switch (element.getAttribute('type')) {
     case 'checkbox':
     return (element.checked === true);
     
     case 'radio':
     var name = element.getAttribute('name'),
     found = false;
     
     if (!name) return (element.checked === true);
     
     form.select('input[type="radio"][name="' + name + '"]').each(
     function(box) {
     if (box.checked === true) {
     found = true;
     throw $break;
     }
     }
     );
     
     return found;
     
     default:
     return !element.value.blank();
     }
     
     case 'textarea':
     return !element.value.blank();
     
     case 'select':
     return (element.selectedIndex > -1);
     }
     }
    });
    
    Element.addMethods('FORM', { 
     validate: function(element, options) {
     element = $(element);
     
     if (Object.isUndefined(options))
     return element.retrieve("validator") || null;
     
     var validator = new Form.Validator(element, options);
     element.store("validator", validator);
     
     return validator;
     }
    });
    
    

    HTML:
    <!DOCTYPE html>
    <html lang="de">
     <head>
     <title>Form.Validator</title>
     <meta http-equiv="Content-Type" content="text/html; Charset=UTF-8" />
     </head>
     <body>
     <form id="test-form" >
     <input type="url" required="1" />
     <select name="test123" required="1" size="2">
     <option value="1">Something</option>
     </select>
     <input type="radio" name="test-radio" value="1" required="1" />
     <input type="radio" name="test-radio" value="2" />
     <input type="checkbox" name="test" required="1" />
     <textarea required="1"></textarea>
     <input type="submit" />
     </form>
     
     <script type="text/javascript" src="prototype.js"></script>
     <script type="text/javascript" src="validation.js"></script>
     <script type="text/javascript">
     // <![CDATA[
     $("test-form").validate({
     onSuccess: function(form) { alert("okay"); },
     onFailure: function(form) { alert("fehler"); },
     onInvalid: function(das_fehlerhafte_feld, der_fehler, form) { /* mach was */ }
     });
     // ]]>
     </script>
     </body>
    </html>
    anpassbar über:
    Code:
    Form.Validator.Types // -> reguläre ausdrücke für input-typen z.b. email, url, tel ...
    Form.Validator.Constraints // -> entsprechende fehlermeldungen
    Form.Validator.Flags // -> validator-funktionen für required="1" oder maxlength="123" usw...
    beispiel:
    Code:
    Form.Validator.Constraints.set("email", { 
     text: "Hier muss eine E-Mail rein Junge!" 
    });
    live: http://murdoc.eu/rr/valid.html

    bekannte fehler:
    felder werden mit dem script nicht mehr resetet, ohne schon
     
  7. Video Script

    Videos zum Themenbereich

    * gefundene Videos auf YouTube, anhand der Überschrift.