On IE8 (not on IE9 or Safari) I get an error
this.text_array is null or not an object
for this line
`if( this.text_array[element].value === '' )`
for this object -
/**
* JClass - Text
*/
var Text = function( form_name )
{
this.text_array = document.forms[form_name].elements;
};
Text.prototype.patterns =
{
prefix_url: /^(http:)|(https:)\/\//,
url: /^.{1,2048}$/,
tweet: /^.{1,40}$/,
title: /^.{1,32}$/,
name: /^.{1,64}$/,
email: /^.{1,64}#.{1,255}$/,
pass: /^.{6,20}$/
};
Text.prototype.checkPattern = function( type )
{
return this.patterns[type].exec( this.text_array[type].value );
};
Text.prototype.checkUrlAdd = function( type )
{
return this.patterns[type].exec( this.text_array.url.value );
};
Text.prototype.checkSameEmail = function()
{
return ( (this.text_array.email.value) === (this.text_array.email1.value) );
};
Text.prototype.checkEmpty = function()
{
var element;
for ( element in this.text_array )
{
if( this.text_array[element].value === '' )
{
return 0;
}
}
return 1;
};
Not sure where to begin troubleshooting this. I guess I could start by hard coding the element object...that would eliminate the DOM Pull as a suspect. I could continue in this way...but I don't have IE8 available right now. Trial and Error unless someone has a bit of insight.
Related SO
for in vs. for
To start your debugging, use console.log(this.text_array); in relevant places.
I can only assume it's caused by this not being set right by the browser. In this case, start your checkEmpty function with var that = this; and use that instead of this through it.
Ended up using
document.getElementById()
for form access as a quick solution to fix the problem. Did not have time to troubleshoot further per suggestions.
Related
I notice that scan is missing from the various transducer libraries that I looked at (e.g transducers-js). Is it impossible to implement or am I missing something?
Actually I can answer my own question. I was trying to implement it in a functional way and I misunderstood how the transducer interface is used. Looking at the source code from transducers-js, they generally keep state in an object and I can implement scan the same way:
var Scan = function( agg, start, xf ){
this.xf = xf;
this.agg = agg;
this.accum = start;
}
Scan.prototype.init = function(){
return this.xf.init();
}
Scan.prototype.result = function(v) {
return this.xf.result(v);
}
Scan.prototype.step = function( result, input ) {
this.accum = this.agg( this.accum, input );
return this.xf.step( result, this.accum );
}
var scan = function(agg, start) {
return function (xf) {
return new Scan(agg, start, xf);
}
}
I'm building a very simple Validation plugin that uses part of the Constraint validation API. I'm using 2 features of the API, valueMissing and patternMismatch like so:
var el = $(this)[0];
if ( el.validity.valueMissing || el.validity.patternMismatch ) {
$errMsg.show();
} else {
$errMsg.hide();
}
I'd like to write my own polyfill so these features work in older browsers as opposed to using an entire HTML5 validation library or plugin.
So I guess what I'm trying to achieve is something like this:
if (!typeof document.createElement( 'input' ).checkValidity == 'function'){
validity.valueMissing = function(){
// check for value
}
}
However, I can't figure out how to go about this, nor how the element is used as the first part of the function:
el.validity.valueMissing
Thanks in advance for any help or suggestions!
There would be a bit of work involved for this poly, but what you need to do, is alter HTMLInputElement's prototype, so each new instance has those methods and propertyes (validity, checkValidity and so on).
Something along these lines:
HTMLInputElement.prototype.testMethod = function (e) { console.log(e) };
HTMLInputElement.prototype.testProp = 'foo';
var input = document.createElement('input');
input.testMethod(input.testProp);
So I finally figured this out with some help from Poelinca's answer...
if ( typeof document.createElement( 'input' ).checkValidity !== 'function' ) {
Object.defineProperty( Element.prototype, 'validity', {
get: function() {
return this;
}
});
Object.defineProperty( Element.prototype.validity, 'valueMissing', {
get: function() {
return this.value !== "";
}
});
Object.defineProperty( Element.prototype.validity, 'patternMismatch', {
get: function() {
var pattern = this.getAttribute('pattern'),
regEx = pattern !== null ? eval( "{/" + pattern + "/}" ) : "";
value = this.value;
if ( regEx !== "" ) {
return regEx.test( value );
} else {
return true;
}
return false;
}
});
}
I used Object.defineProperty so I could call the function without using the parenthesis, which is how the native Constraint validation API works:
if ( el.validity.valueMissing || el.validity.patternMismatch ) { //...
From my research I learned that extending the DOM like this isn't really recommended as you can run into a lot of issues. For details read this article. The comments are particularly helpful as this article is quite dated.
In my case what I'm trying to do is quite simple and I only need to support IE8+. Maybe a little overkill but I like it.
Please feel free to improve / critique my answer!
Thanks!
var ifChecks = function( i )
{
if( i === 23 )
{
// implementation
}
else if ( i === 300 )
{
// implementation
}
else if ...
}
I have this kind of long if else chain (app. 60 checks) in javascript code, this lengthy chain is inefficient as if 60th check comes as input, then it has to unnecessarily go through 59 checks, so I thought to implement like this.
var implobj = { 23 : handleimpl1,
300 : handleimpl2,
.
.
.
}
var handleImpl = function( i )
{
implobj[i]();
}
Is there any other way better than this solution which can be implemented in javascript?
Note: input is not sequential number, otherwise I could have used array instead of object.
I would use your idea, coded slightly differently like this:
var handleImpl = (function() {
var implobj = {
23 : handleimpl1,
300 : handleimpl2,
// ...
defaultImpl: someDefaultFn
}
return function(i) {
(implobj[i] || implobj.defaultImpl)();
};
}());
I wanted to write some pure javascript to better understand it (I realize in "real practice" that frameworks such as jQuery are much more advised and applicable, but this isn't really about how to use frameworks, more about how pure javascript works and best practices).
Anyways, I wrote some simple javascript code. I wanted to create a set of groups of buttons that had one state at a time from the set {on,off} and each state would map to a corresponding function to be fired upon entering that state. Each group of buttons within the master set could contain only one button in the on state at a time. The concept is similar to the idea of radio buttons. Why not use a radio button then? Well semantically it's just suppose to be some buttons for some control elements, but either way I suppose I could have but the question isn't really about that.
The thing is, to pull this off, I added a lot of custom attributes to specific button elements by id in my Javascript. I was doing some research, and found this question and this question, regarding using custom attributes on DOM node (objects). They seem to advocate against such a practice, one even goes so far as to say that doing so could cause potential memory leaks depending on the browser's implementation.
However, for each button I create I need to keep track of a lot of attributes, and if I expand this script I may have to add even more. So what's the best way around storing them on the DOM node but still keeping track of everything and being able to use this in attached functions, etc. al?
It wasn't readily obvious to me how to do this without at the minimum storing a reference of a well name spaced object to the DOM node button element.
I was able to see that from this question jQuery has some way to do this, but I want to know how this is done with just pure javascript.
Here's the full sample code I am working with:
<!DOCTYPE html>
<html>
<head>
<title>Button Test Script</title>
<script language="javascript" type="text/javascript">
window.button_groups = {};
function isset( type ) {
return !(type==='undefined');
}
function debug( txt ) {
if( !isset(typeof console) ) {
alert( txt );
} else {
console.log(txt);
}
}
function img( src ) {
var t = new Image();
t.src = src;
return t;
}
function turnGroupOff( group ) {
if( isset(typeof window.button_groups[group]) ) {
for( var i = 0; i < window.button_groups[group].length; i++ ) {
if( window.button_groups[group][i].toggle == 1)
window.button_groups[group][i].click();
}
}
}
/**
* buttonId = id attribute of <button>
* offImg = src of img for off state of button
* onImg = src of img for on state of button
* on = function to be fired when button enters on state
* off = function to be fired when button enters off state
*/
function newButton( buttonId, offImg, onImg, group, on, off ) {
var b = document.getElementById(buttonId);
b.offImg = img( offImg );
b.onImg = img( onImg );
b.on = on;
b.off = off;
b.img = document.createElement('img');
b.appendChild(b.img);
b.img.src = b.offImg.src;
b.group = group;
b.toggle = 0;
b.onclick = function() {
switch(this.toggle) {
case 0:
turnGroupOff( this.group );
this.on();
this.toggle = 1;
this.img.src = this.onImg.src;
break;
case 1:
this.off();
this.toggle = 0;
this.img.src = this.offImg.src;
break;
}
}
if( !isset(typeof window.button_groups[group]) )
window.button_groups[group] = [];
window.button_groups[group].push(b);
}
function init() {
var on = function() { debug(this.id + " turned on") };
newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1',
on,
function() { debug(this.id + " turned off"); }
);
newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1',
on,
function() { debug(this.id + " turned off (diff then usual turn off)"); }
);
newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2',
on,
function() { debug(this.id + " turned off (diff then usual turn off2)"); }
);
newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2',
on,
function() { debug(this.id + " turned off (diff then usual turn off3)"); }
);
}
window.onload = init;
</script>
</head>
<body>
<button id="button1" type="button"></button>
<button id="button2" type="button"></button>
<br/>
<button id="button3" type="button"></button>
<button id="button4" type="button"></button>
</body>
</html>
UPDATE
The jQuery thing was a bit overkill for my purposes. I don't need to extend an arbitrary element. I have a good idea of how that is done now specific to jQuery (with the arbitrary randomly named attribute storing a cache index integer).
I know ahead of time which host elements I need to extend, and how; also I can/want to setup an id attribute on each of them on the HTML side.
So, inspired by the jQuery setup, I decided to also create a global cache variable except I am going to use the DOM node's id attribute as my cache key. Since it should be a unique identifier (by definition), and I have no plans to dynamically alter id's ever, this should be a simple task. It completely divorces my Javascript objects from the DOM objects, but it does make my code look quite a bit uglier and difficult to read with the many calls to data. I present the modifications below:
<!DOCTYPE html>
<html>
<head>
<title>Button Test Script</title>
<script language="javascript" type="text/javascript">
window.button_groups = {};
function isset( type ) { // For browsers that throw errors for !object syntax
return !(type==='undefined');
}
var c = { // For browsers without console support
log: function( o ) {
if( isset(typeof console) ) {
console.log(o);
} else {
alert( o );
}
},
dir: function( o ) {
if( isset(typeof console) ) {
console.dir(o);
}
}
};
function img( src ) { // To avoid repeats of setting new Image src
var t = new Image();
t.src = src;
return t;
}
var cache = {};
function data( elemId, key, data ) { // retrieve/set data tied to element id
if(isset(typeof data)) {// setting data
if(!isset(typeof cache[elemId]))
cache[elemId] = {};
cache[elemId][key] = data;
} else { // retreiving data
return cache[elemId][key];
}
}
var button_groups = {}; // set of groups of buttons
function turnGroupOff( group ) { // turn off all buttons within a group
if( isset(typeof window.button_groups[group]) ) {
for( var i = 0; i < window.button_groups[group].length; i++ ) {
if( data(window.button_groups[group][i].id, 'toggle') == 1)
window.button_groups[group][i].click();
}
}
}
/**
* buttonId = id attribute of <button>
* offImg = src of img for off state of button
* onImg = src of img for on state of button
* on = function to be fired when button enters on state
* off = function to be fired when button enters off state
*/
function newButton( buttonId, offImg, onImg, group, on, off ) {
var b = document.getElementById(buttonId);
data( b.id, 'offImg', img( offImg ) );
data( b.id, 'onImg', img( onImg ) );
data( b.id, 'on', on );
data( b.id, 'off', off );
var btnImg = document.createElement('img');
btnImg.src = data( b.id, 'offImg' ).src;
data( b.id, 'img', btnImg );
b.appendChild( btnImg );
data( b.id, 'group', group );
data( b.id, 'toggle', 0 );
var click = function() {
switch(data(this.id,'toggle')) {
case 0:
turnGroupOff( data(this.id,'group') );
(data(this.id,'on'))();
data(this.id,'toggle',1);
data(this.id,'img').src = data(this.id,'onImg').src;
break;
case 1:
(data(this.id,'off'))();
data(this.id,'toggle',0);
data(this.id,'img').src = data(this.id,'offImg').src;
break;
}
}
b.onclick = click;
if( !isset(typeof window.button_groups[group]) )
window.button_groups[group] = [];
window.button_groups[group].push(b);
}
function init() {
var on = function() { c.log(this.id + " turned on") };
newButton( 'button1', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group1',
on,
function() { c.log(this.id + " turned off"); }
);
newButton( 'button2', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group1',
on,
function() { c.log(this.id + " turned off (diff then usual turn off)"); }
);
newButton( 'button3', 'images/apply-off.jpg', 'images/apply-on.jpg', 'group2',
on,
function() { c.log(this.id + " turned off (diff then usual turn off2)"); }
);
newButton( 'button4', 'images/unapply-off.jpg', 'images/unapply-on.jpg', 'group2',
on,
function() { c.log(this.id + " turned off (diff then usual turn off3)"); }
);
}
window.onload = init;
</script>
</head>
<body>
<button id="button1" type="button"></button>
<button id="button2" type="button"></button>
<br/>
<button id="button3" type="button"></button>
<button id="button4" type="button"></button>
</body>
</html>
UPDATE 2
I found that through using the power of closure I truly only need to store one "special" attribute, that is the group the button belonged to.
I changed the newButton function to the following, which through closure, eliminates the need to store many of those other things I was:
function newButton( buttonId, offImg, onImg, group, on, off ) {
var b = document.getElementById(buttonId);
offImg = img( offImg );
onImg = img( onImg );
var btnImg = document.createElement('img');
btnImg.src = offImg.src;
b.appendChild( btnImg );
data( b.id, 'group', group );
var toggle = 0;
var click = function(event) {
switch(toggle) {
case 0:
turnGroupOff( data(this.id,'group') );
if( on(event) ) {
toggle = 1;
btnImg.src = onImg.src;
}
break;
case 1:
if( off(event) ) {
toggle = 0;
btnImg.src = offImg.src;
}
break;
}
}
b.onclick = click;
if( !isset(typeof window.button_groups[group]) )
window.button_groups[group] = [];
window.button_groups[group].push(b);
b = null;
}
You either extend objects (which is bad for host objects) or you wrap the objects as jQuery does, using the wrapped object to identify associated data in the hash table. In essence you hash the DOM node and do a lookup in a hash table for the associated data. Of course you still need to extend the host object, but you add only a single property which you know to be reasonably safe to add across browsers, rather than a set of arbitrary properties. If you inspect an element with associated data you might see something like element.jQuery171023696433915756643, which contains the internal storage index for that element. I would recommend reading the jQuery source if you are that interested, particularly the data() function
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var privateCache, thisCache, ret,
internalKey = jQuery.expando,
getByName = typeof name === "string",
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
isEvents = name === "events";
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
elem[ internalKey ] = id = ++jQuery.uuid;
} else {
id = internalKey;
}
}
if ( !cache[ id ] ) {
cache[ id ] = {};
// Avoids exposing jQuery metadata on plain JS objects when the object
// is serialized using JSON.stringify
if ( !isNode ) {
cache[ id ].toJSON = jQuery.noop;
}
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
privateCache = thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
// cache in order to avoid key collisions between internal data and user-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// Users should not attempt to inspect the internal events object using jQuery.data,
// it is undocumented and subject to change. But does anyone listen? No.
if ( isEvents && !thisCache[ name ] ) {
return privateCache.events;
}
// Check for both converted-to-camel and non-converted data property names
// If a data property was specified
if ( getByName ) {
// First Try to find as-is property data
ret = thisCache[ name ];
// Test for null|undefined property data
if ( ret == null ) {
// Try to find the camelCased property
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
I found this article on javascript design patterns that may give you some ideas. Have a look at The Prototype Pattern, this allows you to reuse methods across instances.
I have a table listing with a 'notes' field in each row. I'd like to be able to update these using ajax and display a little message once they have been updated, but I'm struggling to figure out the correct code.
My plan was to capture a key press, and pass the note ID into a timer, which would be reset every time the user presses a key so it will only run once they've stopped typing for 1 second. The problem is, with multiple notes on the page I need to pass it into an array and reset the timer on each one, if this is even possible?
Here's my code:
var waitTime = 1000;
var click = false;
var timers = new Array();
$('.notes').keyup(function(){
var timerVariable = $(this).attr('id').split("-");
timerVariable = timerVariable[0];
timerVariable = timerVariable.replace('note', '');
timers.push(timerVariable);
timers[timerVariable] = timerVariable;
if(click==false){
var id = $(this).attr('id');
if(click==false){
click= true;
timerVariable = setTimeout(function(){doneTyping(id)}, waitTime);
}
}
});
$('.notes').keydown(function(){
for (var timer in timers) {
clearTimeout(timer);
}
click = false;
});
function doneTyping (id) {
var staffNo = id.split("-");
staffNo = staffNo[0];
staffNo = staffNo.replace('note', '');
var data = 'data='+id+'¬e='+$('#'+id).val();
$.ajax({
url: "update-notes.php",
type: "GET",
data: data,
cache: false,
success: function (html) {
jGrowlTheme('mono', 'Updated ' + staffNo, 'Thank you, the note has been updated.', 'tick.png');
}
});
}
I'm wondering if the problem is maybe with the way I'm calling the for loop, or something else? Any advice would be very welcome, thank you!
This is how I do it:
var t;
$(document).ready(function() {
$('#search_string').keyup(function() {
clearTimeout (t);
t = setTimeout('start_ajax()', 3000);
});
});
start_ajax() {
// Do AJAX.
}
It is not a direct answer to your problem but I would personally make a jquery plugin out of your code that you would use like this:
$('.note-fields').myNoteAjaxPlugin({ waitFor: '1000' });
Each "note field" would have it's instance of the plugin encapsulating a timer dedicated for each field. No need to worry about storing in arrays and such.
There are plenty of plugin patterns and boilerplates out there like this one and this other one.
Here is a sample implementation. I've used the one boilerplate and merged it with the jquery ui bridge code (which checks for private methods, re-using a previous plugin instance or instantiating it correctly):
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = 'myNoteAjaxPlugin',
defaults = {
waitFor: "1000",
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.$element = $(element);
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this._timer = null;
this._click = false;
this._init();
}
Plugin.prototype._init = function () {
var self = this;
this.$element.keyup(function(e){
if( self._click === false ){
var id = self.element.id;
if( self._click === false ){
self._click = true;
self._timer = setTimeout(function(){self._doneTyping(id)}, self.options.waitFor);
}
}
});
this.$element.keydown(function(e) {
if (self._timer) {
clearTimeout(self._timer);
}
self._click = false;
});
};
Plugin.prototype._doneTyping = function(id) {
alert('done typing');
};
$.fn[pluginName] = function( options ) {
var isMethodCall = typeof options === "string",
args = Array.prototype.slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.extend.apply( null, [ true, options ].concat(args) ) :
options;
// prevent calls to internal methods
if ( isMethodCall && options.charAt( 0 ) === "_" ) {
return returnValue;
}
if ( isMethodCall ) {
this.each(function() {
var instance = $.data( this, pluginName ),
methodValue = instance && $.isFunction( instance[options] ) ?
instance[ options ].apply( instance, args ) :
instance;
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, pluginName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, pluginName , new Plugin( this , options) );
}
});
}
return returnValue;
};
})( jQuery, window, document );
$('#myinput').myNoteAjaxPlugin({waitFor: '1500'});
Working DEMO
The problem could very well be with this section of code:
$('.notes').keyup(function(){
var timerVariable = $(this).attr('id').split("-");
timerVariable = timerVariable[0];
timerVariable = timerVariable.replace('note', '');
timers.push(timerVariable);
timers[timerVariable] = timerVariable;
if(click==false){
var id = $(this).attr('id');
if(click==false){
click= true;
timerVariable = setTimeout(function(){doneTyping(id)}, waitTime);
}
}
});
I'm not really sure why you're doing timers.push(timerVariable); followed immediately by timers[timerVariable] = timerVariable; - they both add timerVariable into the array, just in (potentially?) different positions.
Also, while I know Javascript allows it, I still think changing the type of a variable is bad practice. Keep timerVariable as the index for your array, and create a new variable when calling setTimeout, rather than reusing timerVariable. It makes your code easier to follow, and reduces the possibility of errors being introduced.
And, finally, call setTimeout then add to your array. Your code isn't doing what you think it is - you're never actually adding the references created by your setTimeout calls to the array. Take a look at this jsFiddle to see what's actually happening.
Consider a more streamlined version of your code:
$('.notes')
.each(function () {
$(this).data("serverState", {busy: false, date: new Date(), val: $(this).val() });
})
.bind("keyup cut paste", function() {
var note = this, $note = $(this), serverState = $note.data("serverState");
setTimeout(function () {
var val = $note.val();
if (
!serverState.busy
&& new Date() - serverState.date > 1000 && val != serverState.val
) {
$.ajax({
url: "update-notes.php",
type: "POST",
data: { data: note.id, note: val },
cache: false,
success: function (html) {
var staffNo = note.id.split("-")[0].replace('note', '');
serverState.date = new Date();
serverState.val = val;
jGrowlTheme('mono', 'Updated ' + staffNo, 'Thank you, the note has been updated.', 'tick.png');
},
error: function () {
// handle update errors
},
complete: function () {
serverState.busy = false;
}
});
}
}, 1000);
});
Initially, the current state of each <input> is saved as the serverState in the .data() cache.
Every event that can change the state of the input (i.e. keyup, cut, paste) triggers a delayed function call (1000ms).
The function checks whether there already is a request in progress (serverState.busy) and backs off if there is (there is no need to hammer the server with requests).
When it's time to send the changes to the server (1000ms after the last event) and the value actually has changed, it posts the new value to the server.
On Ajax success it sets serverState to the new value, on error it doesn't. Implement error handling for yourself.
So every key press triggers the function, but only 1000ms after the last key press that actually made a change to the value change is pushed to the server.