event components on page become unresponsive after page refresh - angularjs

After I refresh my page(the refresh simply sets the events array to [], then refetches the data to be displayed on screen again), the event components still show, but become unresponsive to touch e.g. no buttons on the event card work and you can not swipe left and right on them. When the app first launches tho, you can interact with the buttons just fine.
The problem occurs after refresh EVERY time, but it also occurs after I go through my other tabs on the screen and come back to the events tab.
this only happens sometimes but I thought it would be important to note.
I have tried to reload the component by adding a *ngIf statement in a parent div of the event card that checks for the event array being empty. I thought this would force the component to reload, but no luck.
I also made sure that the event's array first gets equal to [] before fetching for more data.
I also made sure that the data the event's array gets filled with is the same before and after the refresh happens.
HTML Code where the event cards are present :
<div class="cards" *ngFor="let event of events; index as i" class="final">
<ion-item-sliding class="item-sliding">
<ion-item-options class="item-options" side="start">
<ion-item-option color="success" (click)="saveEvent(event.id)">Mark As Going</ion-item-option>
</ion-item-options>
<app-card *ngIf="event.eventDeleted != true" [id]="event.id"></app-card>
</ion-item-sliding>
</div>
Some relevant global variables inside my TS file :
limit: number = 5;
lastVisible: any;
This method gets run first :
ngOnInit() {
this.fetchEvents();
}
My method for pulling events :
fetchEvents() {
this.fStore.collection('events').ref.limit(this.limit).get().then((events) => {
this.lastVisible = events.docs[events.docs.length-1];
this.events = events.docs
console.log(this.events, "events")
});
}
Problem occurs after this refresh button
doRefresh(event) {
this.fetchEvents()
setTimeout(() => {
event.target.complete();
}, 2000);
}
Expected: After refresh, buttons should be interactive.
Actual: After refresh, buttons are not interactive.

This sounds like ionic/issues/15486 which is basically:
dynamically generated ion-item-sliding becoming unresponsive after removing elements from base array
The issues claims that this was fixed in ionic#4.1.0... Which version are you using?

Related

Execution order of protractor tests

Which is the best way to control the execution order of Protractor tests?
Problem case 1: protractor swipes angular pages so quickly that it cannot manipulate (fill in) input data. (The swipe logic is to transfer from being invisible to visible and by translating them into the window visible area). thus protractor cannot see those beyond the window and with opacity 0. To test i can only fill in the first page. the others are swiped too fast (or async).
Problem case 2: After i filled in the first page and submitted the form, the data is saved and alert shows confirmation message. Then protractor has to click a drop-down list and browser should navigate to the page where saved data is displayed. The problem is that protractor clicks the drop-down list before the data is saved due to alert fired later (have to wait for alert).
Question is: Is there a way to control tests to be executed in the given order in protractor? And is there a way to hold down swiping to fill in the date (otherwise protractor does not see it)? Here is the simplified code:
it('should fill in form and send data', function() {
// fill in textarea field
element.all(by.css('textarea')).first().clear().sendKeys('test');
// goes to page 2
browser.executeScript("angular.element(document.documentElement)
.injector().get('$rootScope').$broadcast('nextPage', {direction: 'next'});");
// Here is Problem 1. Though i can see this page when testing,
// the input is not visible for protractor.
// fill in input-field
element.all(by.css('input')).first().clear().sendKeys('test');
// goes to page 1
browser.executeScript("angular.element(document.documentElement)
.injector().get('$rootScope').$broadcast('prevPage', {direction: 'prev'});");
// submit form
element(by.css('button[type=submit]')).click();
// Here is problem 2. The following test is executed earlier
// than the following alert.
//browser.wait(protractor.ExpectedConditions.alertIsPresent(), 3000);
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toEqual('Saved');
alertDialog.accept();
});
it('should click drop-down-list', function() {
// click drop-down list
element(by.css('.drop-down-list')).click();
});
I personally believe browser.sleep(5000) should resolve the second issue.
if not can you try with promisies
element(by.css('button[type=submit]')).click().then(function(){
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toEqual('Saved');
alertDialog.accept();
});
This should wait for the promise to get resolved (i.e to click submit button) and then execute the code snippet inside

CSS keyframe animations only running once in Angular app

Updated: In short, my problem seems to be that the DOM stops updating whenever I am calling a function which switches from one state to another in the application.
More interestingly though, this actually only happens the second time this function is called from one particular element.
For specific details, I'm developing an single page app in AngularJS 1.2.28 and one I've stripped my html template, display-widgets.html to contain only 3 elements as follows:
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[0].widget)">widget 1</p>
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[0].widget)">also widget 1</p>
<p data-ng-click="widgetCtrl.onWidgetSelected(widgetCtrl.model.widgetData[1].widget)">widget 2</p>
<p>{{widgetCtrl.interstitialMode}}</p>
The controller looks something like as follows:
widgetShop.widgetCtrl.prototype.showWidgetHome = function (widget) {
this.state.go('widgetHome', {
widgetId: widget.widgetID
});
};
widgetShop.widgetCtrl.prototype.setupInterstitial = function () {
this.interstitialMode = true;
};
widgetShop.widgetCtrl.prototype.onWidgetSelected = function(widget) {
this.setupInterstitial();
this.showWidgetHome(widget);
};
The intended functionality is that after the widget is selected, onWidgetSelected() is called passing the selected widget as a parameter where the animation variable is updated (with the intention to add class with ng-class in the DOM) with setupInterstitial() before another function is called to switch the state. Some interaction will occur on this state, (e.g. add widget to basket) and once complete, the state is switched back to the display-widgets state.
I've stripped the html down here to be just p tags for testing but I've found if you select the same widget(p tag) again, the element is NOT updated in the page as before, and more interesting still is that no DOM updates occur at all. Console logs and alerts show me that angular has updated the correct variables in the controller but the state transition seems to prevent any DOM updates occurring. Selecting the widget 2 will update the DOM as expected, but as with the first widget, this will not occur more than once. Selecting 'also widget 1' after selecting widget 1 will not update the DOM as though it were the second time it were selected, but selecting this before widget 1 will have the same affect as selecting widget 1 first.
Does anybody know if this is a specific issue with browsers not performing animation again because it is a single page app? Or is there likely to be an issue elsewhere in my code? I haven't been able to find anything via google or stack overflow with people having a similar issue.
Sounds like once you've added the class, it is never removed again.
Once the class is added, the animation starts. When you add it again nothing changes because it already had that class and all the styling was already applied.

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Backbone-route click doesn't not respond on 2nd attempt

I am doing form validation in a function when a user wants to preview an invoice which is called by a route:
routes: {
"new" : "newInvoice",
"new/:assignmentid" : "newInvoiceAssignment",
"edit/:invoiceid" : "editInvoice",
"preview/:invoiceid" : "previewInvoice",
"preview" : "preview",
"delete/:invoiceid" : "deleteInvoiceModal",
"whyCant" : "whyCant",
"whatsThis" : "whatsThis"
},
And here is my two buttons (actually, a button and an href) on the form:
<div class="span8 alignRight">
<button id="saveInvoiceDraft" type="submit" class="btn btn-warning">Save Draft</button>
<a id="previewInvoice" class="btn btn-primary">Preview & Send</a>
</div>
When this invoice is created, the URL for the tag is set with:
var url = '#preview';
$('#previewInvoice').attr('href',url);
And finally, when I click on the "Preview & Send" button, the previewInvoice(invoiceid) function below runs, properly catches the one form field missing and displays the error message. At that point even if I populate the form field, that button is dead and no longer responds. However, the "Save Draft" button works perfectly and mimic's the same code as in the previewInvoice() function.
I know there is probably a better way to do this, but I was following that way it was done in another section of the app I inherited. Actually, as I am typeing this I am wondering since the sendDraft() function works and its a button and the previewInvoice() function does not, the fact that it is a href might have something to do with it.
function previewInvoice(invoiceid) {
var invoice = new Invoice({"invoiceid": invoiceid});
invoice.set({"invoiceid": invoiceid,"invoicestatus": "draft"});
formGetter(invoice);
validateInvoiceForm(invoice);
if (window.errors.length == 0) {
//business logic here
if (window.panel == undefined) {
// business logic here
}
else {
//save business logic here
}
}
else {
showInvoiceErrors();
}
}
Any ideas why the button no longer responds? I am not seeing any error's in the console. I added a consol.log inside the function to display the value of a different form element, and it displays the first time in the console, but if I change the data and click the button again, that log does not update, which to me is another clue that it is just not firing.
Backbone.History listens to hashchange events to trigger navigation to routes. When you first click the previewInvoice button, the URL hash fragment is set to #preview, and the matching route is triggered.
When you click the same button the second time, the hash doesn't actually change, and therefore the router doesn't catch it.
I'm having a hard time recommending a good solution to this problem. Normally I would recommend catching the click event and calling router.navigate("preview", {trigger:true}); manually. However, based on your code sample it looks like your application is built around the Router, and there isn't a View layer for DOM event handling as you would expect in most Backbone applications.
On the Router level this is a bit trickier to solve. You could use router.navigate to set a dummy hash after the preview route has been executed. This would cause the link to trigger a hashchange on the second time as well. Unfortunately this would mean that the preview page would not be bookmarkable, and since you're not using pushState, would leave an extraneous history entry.
I'm afraid this issue will have to either be solved with a hacky fix (as outlined above) or a major refactoring.
I have this same problem, my solution is to put in a handler that does a fake "in-between" route that is hidden from history, so that Backbone.history will register the navigation as a change and trigger the action.
Put a class on links that you need the route action to trigger regardless if the URL is the same:
do it
In your Backbone view, put in an event handler:
events: {
"click .js-ensureNav": "_ensureNav",
},
Actual handler:
_ensureNav: function (event) {
var route_name = $(event.target).attr('href').slice(1);
Backbone.history.navigate("fake");
Backbone.history.navigate(route_name, {trigger: true, replace: true});
},
Instead of
var url = '#preview';
$('#previewInvoice').attr('href',url);
Try this
var date = new Date();
var url = '#preview?date:' + date ;
$('#previewInvoice').attr('href',url);
So Every time new request will be generated and this will solve your problem.

Backbone.js 'swallows' click event if another event triggers a re-render

What I want to achieve is that on form changes, the whole view should be re-rendered. This is to provide a preview of the data just edited, and to hide certain elements in the form when check boxes are ticked.
When the user edits the field and clicks on the button without leaving the filed first two events are fired at the same time: change, click. The change handler first updates the model, which triggers a re-render of the form. When it's the click events turn, nothing happens. I guess it has to do with the re-render because when I comment out the
#model.on 'change', #render, #
Both event handlers are executed as it should be.
Maybe the click handler is not executed because the click target has been removed from dom and a new button has been added? How would I fix this? I was thinking the code I wrote was 'idiomatic' Backbone.js, but I'm still learning :-)
Here is a simplified version of my code showing the problem:
jsbin
Let us add a few things so that we can see what's going on. First we'll mark the Save button with a unique ID:
render: ->
id = "b#{Math.floor(Math.random() * 1000)}"
console.log('button id = ', id)
#...
And then we can see which button was hit:
save: ->
console.log('pressed = ', #$('button').attr('id'))
#...
We'll also add a global click handler to watch the <button> outside of the Backbone stuff:
$(document).on('click', 'button', ->
console.log('global click = ', #id)
)
Live version: http://jsbin.com/oviruz/6/edit
Play around with that version a bit and you might see what is going on:
Change the content of the <input>.
Try to click Save.
As soon as the <input> loses focus, the change event is triggered.
That event calls fieldChanged which does #model.set(...).
The #model.set call triggers Backbone's events, in particular, the #model.on(...) from the view's initialize.
The Backbone event sends us into render which does a #$el.html(...) which replaces both the <input> and the <button>.
The html call kills all the DOM elements inside the view's el. But, and this is a big but, the browser needs to get control again before this process finishes.
Now we're back into the event queue to deal with the click on Save. But the <button> we're clicking is a zombie as the browser's work queue looks like this: deal with the click event, replace the DOM elements from 3.4. Here the work from 3.4 isn't complete so the <button> that you're clicking is half in the DOM and half dead and won't respond to any events.
You have two event queues fighting each other; your Backbone events are changing the DOM behind the browser's back and, since JavaScript is single threaded, the browser is losing and getting confused.
If you delay the #$el.html call long enough to let the browser catch up:
set_html = =>
#$el.html """
<input type="text" id="text" value="#{#model.get('foo')}"/>
<button class="save" id="#{id}">Save</button>
"""
setTimeout(set_html, 1000) # Go higher if necessary.
You'll get the behavior you're expecting. But that's an awful, horrific, nasty, and shameful kludge.
Messing around with the DOM while you're still processing events on those DOM elements is fraught with danger and is little more than a complicated way to hurt yourself.
If you want to validate the field when it changes and bind the view's render to "change" events on the model, then I think you'll have to do the validation by hand and use a silent set call:
fieldChanged: (e) ->
field = #$(e.currentTarget)
#model.set({ foo: field.val() }, { silent: true })
// #model.validate(#model.attributes) and do something with the return value
If you do a #model.save() in the Save button's callback, the silent changes will be validated en mass and sent to the server. Something like this: http://jsbin.com/oviruz/7/edit
Or you skip the #model.set inside fieldChanged and just use #model.validate:
fieldChanged: (e) ->
val = #$(e.currentTarget).val()
// #model.validate(foo: val) and do something with the return value
and leave all the setting stuff for save:
save: ->
#model.save(foo: #$('#text').val())
Something like this: http://jsbin.com/oviruz/8/edit
You can add a little delay before update model in fieldChange, you can replace change event with keyup. There might be many workarounds, but probably best was would be not to re-render whole view on model change.

Resources