Webshims - Show invalid form fields on initial load - backbone.js

(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')
;
});

Related

How do I return focus to an element when the entire page changes?

I have a complicated setup. My application is driven by a set of "rules" which dictate what the user interface is. The UI is rendered by looping through the rules and creating the individual dropdowns. Initially, everything renders properly. However, once a user makes a change to the UI, other rules may be affected. In my application, an api call is made, which then returns a modified set of rules. In the attached plunker, I've simplified things such that only the new set of rules is applied, which causes the page to re-render. The problem is that my users would like to be able to tab between all of the entries on the page and make changes. However, once the page is re-rendered, the currently selected page element is now gone nothing has the focus. I've tried to put the focus back on the proper element by tracking a common Id, but to no avail.
Using either of these doesn't seem to work.
var el = document.getElementById(focusId);
el.focus();
angular.element(el).focus();
I've also tried using the autofocus attribute on the dropdown that I want to have focus, but that didn't work either. I'm using angularjs 1.2. Any ideas are appreciated.
http://plnkr.co/edit/ND9PKqULIOlixWR4XChN?p=preview
If you want to assign auto focus dynamically to a element on the DOM from angular you can use this:
var myEl = angular.element(document.querySelector('select'));
myEl.attr('autofocus',"attr val");
You can also pass in an id like: angular.element(document.querySelector('#focusId'));
You can look here for a prior answer which may be of some more help!
-Cheers!
Problem here is, you are trying to focus the element before the blur event completes. So you need to execute the focus code after blur event. Async execution of your focus code would solve the problem. You can use either setTimeout or $timeout.
setTimeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
or
$timeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
dont forgot to inject $timeout to your controller if you are using second code. Hope this helps :)

jQuery 1.7.2 and IE7 on() doesn't seem to fire for selects

I have a table of rows where are dynamically added, but for some reason the on() event doesn't seem to fire when I change the value of a select.
For simplicity, I wrote the following jsFiddle. This is a VERY simplistic example of what I am doing. I do have to note that I was unable to verify if the jsFiddle failed the same way in IE7 because jsFiddle does not seem to work in IE7 at all.
The basics of my code are this...
First, the page loads with 1 or more of these rows, which represent a complex "subform" to be posted to Spring MVC. The subform has a group of 5 SELECT tags that have an event handler attached at the top TABLE as such:
$('#parentTable').on('change', 'div.additional div[class^="additional"] select', function() {
// ...enable/disable siblings according to value
}
At the bottom of the table is an Add button. When the user clicks it, I take the last visible row, clone it, use regex to fix the index on the name/id, and set all the fields blank and disables except one field, which is used to do a lookup. This all has the appearance of working.
The user enters a value in the lookup field, clicks Lookup, which performs an AJAX call, filling in the subform then calling the change on the selects to set their default state like so:
$subTable.find(':input:not(:button)').change();
This fires the above on() as expected. However, if I physically click on the resulting SELECTs and change their values the on() event handler is not called.
I have verified that the resulting cloned table row looks/smells/feels correct, at least from using IE8's Developer Tools (I have IE8 in IE7 compatibility mode for testing, because IE7 has VERY limited testing and the problem seems to be present there too). When I run this in Chrome it works perfectly fine, however I am aware the clone is technically working differently.
Any help would be appreciated.
Update
I tried swapping the selector to drop the class attr as suggested below:
$('#parentTable').on('change', 'div.additional div select', function() {
// ...enable/disable siblings according to value
}
And I tried all caps like the referenced post:
$('#parentTable').on('change', 'div.additional div[class^="ADDITIONAL"] select', function() {
// ...enable/disable siblings according to value
}
The first worked the same way, the second did nothing at all (disabled all on() change handling).
Will continue to play with this until I get it working. There has to be something simple going wrong here.
Update 2
I tried adding a class to all the `SELECT`s, and added the following two:
$('select.addedClass').on('change', function() {
alert("Select change:" + $(this).val());
});
$('#parentTable').on('change', 'select.addedClass', function() {
alert("Select change 2:" + $(this).val());
});
Here is the process I performed:
Reloaded page, both alerts fire for the single row there
Added a row
Performed AJAX lookup, which calls $addedTable.find(':input:not(:button)').change(); as described above, both events fire
Change the value of one of the selects, neither alert fires.
I would expect the first to fail, since the event listener is connected directly to the SELECT and not a higher level parent, but the second one fails the same way. So, in the Developer Tools, I tried to run the following:
$('#order.itemList1.additionalLine1').change()
Which is the ID of one of the selects I added, and still nothing fired. It's as if the event handler is ignoring all added SELECTs.
To make sure the select was working on the on(), I did this in the console after I added the new row:
$('#parentTable').find('div.additional div[class^="additional"] select').each(function() {
alert($(this).attr('name') + '(' + $(this).prop('id') + ')' + ' : ' + $(this).val());
});
Update 3
I ended up putting in a horrible hack that, for IE7 only, drops and re-adds the change handler directly on the SELECTs. It's ugly, but I have to support IE7, so...
I am leaving this open to see if anyone has a better solution, or might know why it's not working.
The problem isn't the change event, or the delegation on select elements.
It's the attribute selector:
div[class^="additional"]
IE7 and below don't do attribute selectors.
I've done a bit of searching and a few people seem to think this can be solved using case-specific syntax.

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.

Show custom pop up warning message when user changes any value (text box/LOV) on page

I have requirement to show custom pop up warning message when user changes any value (text box/LOV) on page and close tab/cancel button by mistake.
Option I tried are:
a) Within application we are using a complex task flow/RegionModel for 7 different scenario's. Also requirement is to display custom message - Hence could not use approach "unsaveddatawarning"
http://www.oracle.com/technetwork/developer-tools/adf/unsaveddatawarning-100139.html
b) Second option I tried was to have custom region controller:
CustomRegionController implements RegionController
Inside validateRegion(RegionContext regionContext) thought to find if page data is dirty
AdfFacesContext.getCurrentInstance().getDirtyPageHandler().isDataDirty();
or
DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
DCDataControl cDataControl = dcBindings.getDataControl();
boolean dirtyFlag = cDataControl.isTransactionModified();
In both scenario it always gives true (seems due to common set of VO/View Link application module always gets dirty when data is being rendered on page load).
Last option I am left with is to invoke valueChangeListener for each element (textbaox, LOV, Check box). I do not like this option at all. Please suggest if there can be better way to handle this scenario.
Why is using a value change listener a problem? Have each input component call the same VCL method in the backing bean. If necessary you can get the component id from the vcl event object.

Display n time ago on various items using jquery, [issue with new elements generated after the loading the DOM]

I am using Jquery plugin http://timeago.yarp.com/ for showing time.
Issue is timeago will not take effect for dynamically generated items.
$(document).ready(function() {
$(".timeago").timeago(); // works perfectly fine for the items which are loaded on page load
//$(".timeago").live(timeago()); // gives me an error ie timeago is not defined
//$(".timeago").live($(".timeago").timeago()); // gives me an error too much recursion.
jQuery.timeago.settings.allowFuture = true;
});
From some google search I got to know something ie:
Using live is the same as using bind, except that it is limited only to the events click, dblclick, keydown, keypress, keyup, mousedown, mousemove, mouseout, mouseover, and mouseup.
Now how can do it cause I dont have any click event? How can I bind this?
.live() and .bind() assign callbacks to event. In your case, you don't have an event to assign the function to and so it fails.
You can, in theory, assign your callback to a custom event. You will however have to manually trigger the event (using .trigger()) whenever your item is generated. For example:
$("abbr.timeago").live("timeago", function() {
$(this).timeago();
});
// ... and in the bit that generates your item
$new_item.trigger("timeago")
Demo: http://jsfiddle.net/ZjuW4/9
Of course, using .live() in this situation is purely academic and does not really serve a good purpose.
If you do have access to the code that's generating the items, you can simply chain in the call to .timeago() as the items are generated, i.e. http://jsfiddle.net/ZjuW4/3/
take a look in this topic
here was discussed how to put timeago on dynamically loaded items
for example the results of an ajax request.
Activate timeago on newly added elements only
PS: allowFuture does not have anything to do with putting timeago on newly created items on your page. it just allows dates in the future (f.e. "in 3 days", "next week")

Resources