#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ß + Multi-Zitat Zitieren
#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. + Multi-Zitat Zitieren
#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. + Multi-Zitat Zitieren
#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. + Multi-Zitat Zitieren
#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. + Multi-Zitat Zitieren
#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 + Multi-Zitat Zitieren