1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import {moduleFor, test} from 'ember-qunit'; import RSVP from 'rsvp'; import wait from 'ember-test-helpers/wait'; moduleFor('controller:login-view', 'Unit | Controller | Login view', { needs: ['service:session'] }); test('some description', function (assert) { let ctrl = this.subject({ session: { login() { return new RSVP.Promise((resolve, reject) => { reject(/* ... */) }) } } }); ctrl.send('someLoginAction'); wait().then(() => { // assert goes here }); }); |
Category Archives for Javascript
Überlaufende Texte finden
Eine bessere Übersetzung Umschreibung für text-overflow ist mir nicht eingefallen 🙃
In jQuery ginge das so:
1 2 3 4 5 6 7 8 |
$.expr[':'].truncated = function (e) { // ggf. Style checken, bspw. // $(e).css('text-overflow') === 'ellipsis' return e.offsetWidth < e.scrollWidth; }; // dann: let items = $('.your-selector:truncated'); |
Cross-post; basiert auf den Antworten hier. Bonus: In Selenium geht das so:
1 2 3 4 5 6 7 8 9 10 |
WebElement element = ...; // element is truncated? double offsetWidth = Double.parseDouble(element.getAttribute("offsetWidth")); double scrollWidth = Double.parseDouble(element.getAttribute("scrollWidth")); assertThat(offsetWidth < scrollWidth).isTrue(); // truncation style is ellipsis? String overflowStyle = element.getCssValue("text-overflow"); assertThat(overflowStyle).isEqualTo("ellipsis"); |
bäm.
Javascript: “Pull to refresh”
Pull-to-refresh in (mobilen!) Webseiten nachzubauen, ist jetzt keine Raketenwissenschaft, aber doch so viel Aufwand, dass sich ggf. eine Library lohnt. Viele (? einige? apeatling, ember-gestures, …) bauen aber auf hammer auf, und das hat einen Bug (Beispiel, es gibt weitere Tickets) im Zusammenspiel von Panning (also dem “Pull” in “Pull-to-refresh”) und Scrolling. Zusammengefasst: Es geht nur eines von beiden. Mit ein wenig Drumherumgehacke bekommt man das etwas näher zusammengeführt, aber entweder kommt PanEnd dann gar nicht (was das “refresh” schwierig macht), oder bspw. die PanMove-Events kommen nicht zuverlässig (wodurch man den “pull” nicht 1:1 an den Finger des Nutzers hängen kann).
Ein npm-Modul, das nicht auf hammer aufbaut, wäre pulltorefreshjs (getestet mit 0.1.11 und 0.1.13):
1 2 3 4 5 6 |
PullToRefresh.init({ mainElement: '.myCssSelector', onRefresh: () => { console.log('hello moto') }, }); |
In Ember sieht das als Komponente so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import Component from '@ember/component'; import PullToRefresh from 'npm:pulltorefreshjs'; import {get, set} from '@ember/object'; import {getOwner} from '@ember/application'; import {inject as service} from '@ember/service'; export default Component.extend({ classNames: ['pull-to-refresh'], router: service(), currentRouteInstance: undefined, didInsertElement() { this._super(...arguments); const owner = getOwner(this); const currentPath = owner.lookup('controller:application').currentPath; set(this, 'currentRouteInstance', owner.lookup(`route:${currentPath}`)); // TODO use unique identifier for mainElement, store result of init() PullToRefresh.init({ mainElement: '.pull-to-refresh', onRefresh: () => { get(this, 'currentRouteInstance').refresh(); }, }); }, willDestroyElement() { this._super(...arguments); // TODO destroy current ptr only; id is returned by init() PullToRefresh.destroyAll(); }, }); |
Auf dem Handy sollte das so schon funktionieren. Auf dem Desktop hatte ich das Phänomen, dass Hochcrollen immer erst beim zweiten mal funktioniert hat (und auch dann nur, wenn zwischen Versuch 1 und 2 nicht zu viel Zeit lag). Weil: Die Lib immer beim Hochscrollen den Loader anzeigt, wenn man die Funktion shouldPullToRefresh
nicht vom default !window.scrollY
ummapt, bspw. auf
1 2 3 4 |
shouldPullToRefresh: () => { let scrollTop = this.$().parent()[0].scrollTop; return scrollTop === 0; }, |
Hochscrollen geht sonst nur, so lange der Loader angezeigt wird 🙃
Generische npm Module in Ember
Wer, wie ich, die verfilzte komplexe Javascript-Umgebung etwas… “unübersichtlich” findet, und sich fragt, wie zur Hölle man ein nicht-Ember-spezifisches npm-Modul in Ember importiert, dem kann geholfen werden:
Erst Browserify:
1 |
npm install ember-browserify --save-dev |
dann, voilà:
1 |
import PullToRefresh from 'npm:pulltorefreshjs'; |
Neues Fenster in Web Apps
Anwendungsfall: Eine Bedienungsanleitung in Form eines PDFs in einem neuen Fenster/Tab öffnen. Internet sagt (hier am Beispiel Ember):
1 |
window.open('anleitung.pdf'); |
Das funktioniert grundsätzlich auch in Fullscreen Web Apps, aber da solche Apps keine Browsernavigation anzeigen, kommt man ggf. „nie wieder“ aus der geöffneten Seite raus. Abhilfe schafft
1 2 3 4 |
var a = document.createElement('a'); a.setAttribute('href', 'anleitung.pdf'); a.setAttribute('target', '_blank'); a.click(); |
, was anleitung.pdf
im „richtigen“ Browser anzeigt, statt innerhalb der App.
Ember.js: Helper erweitern
Angenommen, ich habe eine Ember-Anwendung mit Zielplattform Handy. Dann möchte ich zentral an einer Stelle für alle Inputfelder Autokorrektur usw. deaktivieren:
1 2 3 4 5 6 7 8 9 10 11 |
// bsplw. in app.js: Ember.TextSupport.reopen({ attributeBindings: ['autocomplete', 'autocorrect', 'autocapitalize', 'spellcheck'], init: function () { this._super.apply(this, arguments); this.set('autocomplete', 'off'); this.set('autocorrect', 'off'); this.set('autocapitalize', 'off'); this.set('spellcheck', false); // WICHTIG: boolean } }); |
Ember.js: Vererbung in Routen
Angenommen, ich möchte ein default Verhalten auf viele Routen vererben, bsplw. das Handling von 403ern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// routes/restricted.js import Ember from "ember"; export default Ember.Route.extend({ actions: { error(result) { if (result.errors[0].status === '403') { this.transitionTo('login'); } else { // Let the route above this handle the error. return true; } } } }); |
Und dann in irgendeiner Route:
1 2 3 4 5 6 7 |
import Restricted from "../routes/restricted"; export default Restricted.extend({ model() { return this.get('store').query(/* ... */); } }); |
Grundsätzlich kommt das von hier, ergänzt um die konkrete Implementierung des Error Handlings. Das findet sich hier in der Doku, wobei error.status
nicht funktioniert, es muss result.errors[0].status
sein.
JS: navigator.language im Chrome
Chrome ist der neue Internet Explorer:
1 2 3 4 5 6 7 8 |
// IE: let language = navigator.userLanguage; // "everyone else" (as found in most tutorials): let language = navigator.language; // actually working: let language = navigator.languages[0] || navigator.language || navigator.userLanguage; |
Stellt sich raus: navigator.language
ist im Chrome die Sprache des UI, nicht “Accept-Language” m(
navigator.languages
funktioniert dafür auch im FF und liefert alle akzeptierten Sprachen.
AngularJS 1.2.14 (ff): $scope.$watchCollection
$watchCollection würde man intuitiv so verwenden (Quelle):
1 |
$scope.$watchCollection(['data.myA', 'data.myB', 'data.myC'], function(newValues, oldValues) {...}) |
Das funktioniert aber nicht. Korrekt wäre ein großer String (Quelle):
1 |
$scope.$watchCollection('[data.myA, data.myB, data.myC]', function(newValues, oldValues) {...}) |
Darüber hinaus gibt es einen Bug in v1.2.14 (und in gewissen Szenarien auch darüber hinaus), bei dem newValues[i] immer gleich oldValues[i] ist. Der Fix dafür hat aber ebenfalls einen Bug: Die Dependecies werden nicht korrekt injected. Folgende Änderungen waren nötig:
1 2 3 4 5 6 7 |
// line 5: .config(function($provide){ .config(['$provide', function($provide){ // line 11: $provide.decorator('$rootScope', function($delegate, $parse) { $provide.decorator('$rootScope', ['$delegate', '$parse', function($delegate, $parse) { // line 142, 144: close Arrays |
Ein Pull Request ist nicht möglich, aber vllt zieht sich der Autor das auf meinen Kommentar hin.
Schlussendlich: Nicht vergessen, die Decorators in die App zu injecten:
1 |
angular.module('myModule', [..., 'Decorators']); |
AngularJS: Reload $state
Während die Lösung
1 |
$state.go($state.current, {}, {reload: true}); |
für mich nicht funktioniert hat, tut es allerdings diese:
1 |
$state.go($state.current.name, {}, {reload: true}); |
Der zweite Parameter hält die state-Params, also zB $state.params.
(Angular v1.2.14, nur zu Referenzzwecken gepostet)