Validation in angular.js ng-repeat

It seems to be a kind of pretty obvious task to make validation for fields generated with angular ng-repeat directive, but actually I think it is not so obvious as I thought. Let say we have a dynamic “table” of “fields”, both fields are numbers and left one should be less then right one. BTW let say that every line of this table is a range, so the next “low” value should be as previous “high” + 1, e.g.:
[1] [2]
[3] [10]
[11] [20]

Let’s create simple form for this:

<div ng-app='formValidationApp'>
<form ng-controller="ValidationCtrl" name='rangesForm' novalidate>
    <div class='line' ng-repeat='line in ranges'>
        low: <input type='text' ng-pattern='/^\d+$/' ng-model='line.low' />
        high: <input type='text' ng-pattern='/^\d+$/' ng-model='line.up' />
        <a href ng-if='!$first' ng-click='removeRange($index)' >Delete</a>
    </div>
    <a href ng-click='addRange()'>Add Range</a>
    <input type='submit' ng-disabled='rangesForm.$invalid'>
</form>
</div>

And here is the controller:

formValidationApp = angular.module('formValidationApp', []);
formValidationApp.controller('ValidationCtrl', ['$scope', function ($scope){
    var scope_ = $scope;
    $scope.ranges = [{low: 1}, {}, {}];
    $scope.removeRange = function (index){
        scope_.ranges.splice(index, 1);
    };
    $scope.addRange = function(){
        scope_.ranges.push({});
    };
}]);

And little bit CSS:

.line {
    clear: both;
}
input.ng-invalid {
    border-color: red;
    color: red;
    background-color: pink;
}

The working code you can find in this fiddle http://jsfiddle.net/82PX4/

Ok, as you may see I’ve added basic angular validation via “ng-pattern”. Also I want to add “validation error text”, as far as you may know interpolation doesn’t work in ng-repeat, I mean you can not set input’s name to something like “low_$index”. As an example http://stackoverflow.com/questions/12044277/how-to-validate-inputs-dynamically-created-using-ng-repeat-ng-show-angular

Ok, but we have “ng-form” for this case, cool let’s use it. I added ng-form to the DIV with the repeater, and added validation error, so here is the updated html:

<div ng-app='formValidationApp'>
<form ng-controller="ValidationCtrl" name='rangesForm' novalidate>
    <div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
        low: <input type='text' ng-pattern='/^\d+$/' ng-model='line.low' />
        high: <input type='text' ng-pattern='/^\d+$/' ng-model='line.up' />
        <a href ng-if='!$first' ng-click='removeRange($index)' >Delete</a>
        <div class='error' ng-show='lineForm.$error.pattern'>
            Must be a number.
        </div>
    </div>
    <a href ng-click='addRange()'>Add Range</a>
    <input type='submit' ng-disabled='rangesForm.$invalid' />
</form>
</div>

And here is the updated jsfiddle – http://jsfiddle.net/82PX4/1/

Ok that’s fine, and now let’s try to add custom validation for inputs in angular ng-repeat directive.
I won’t create angular directive, because initially the main goal of directive is “reusability”. Let say that this “range” validation is completely custom thing and this is the only usage of such kind of logic. The creation of directive will simplify the implementation of this task, but for me it’s kind of “hack” in this case, the logic is very custom and should be in controller\service\etc but not in directive.

The most efficient way (in terms of angular) to validate our “model” (I mean $scope.ranges) is to watch $scope.ranges and to validate it (the logic will operate only with data, and will watch only data), but afterward, when for example first range will be invalid, it is needed to set validity for this element and this is the problem.

It is impossible to access defined (let say the second) form (lineForm) because interpolation for form’s name doesn’t work, so it is impossible to make something like this:

rangesForm.lineForm[2].low.$setValidity('ranges', false);

or like this

rangesForm.lineForm_2.low.$setValidity('ranges', false);

That’s a pity, I hope I just didn’t find the right solution to access element by index.

The only solution that I found is to use “ng-change” for tracking changes (instead of watching the data model) and to pass “this” to the ng-change handler (so we can access the scope of current ng-form).

So, lets modify the code. Here is the ng-repeat section:

<div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
        low: <input type='text' 
                    name='low'
                    ng-pattern='/^\d+$/' 
                    ng-change="lowChanged(this, $index)" ng-model='line.low' />
        up: <input type='text' 
                    name='up'
                    ng-pattern='/^\d+$/'
                    ng-change="upChanged(this, $index)" 
                    ng-model='line.up' />
        <a href ng-if='!$first' ng-click='removeRange($index)' >Delete</a>
        <div class='error' ng-show='lineForm.$error.pattern'>
            Must be a number.
        </div>
        <div class='error' ng-show='lineForm.$error.range'>
            Low must be less the Up.
        </div>
    </div>

And here is updated js:

formValidationApp = angular.module('formValidationApp', []);
formValidationApp.controller('ValidationCtrl', ['$scope', function ($scope){
    var scope_ = $scope;
    $scope.ranges = [{low: 1}, {}, {}];
    $scope.removeRange = function (index){
        scope_.ranges.splice(index, 1);
    };
    $scope.addRange = function(){
        scope_.ranges.push({});
    };
    $scope.lowChanged = function(scope, index) {
        // check ranges
        setValidity(
            scope.ranges[index].low,
            scope.ranges[index].up,
            scope.lineForm.low);
 
    };
    $scope.upChanged = function(scope, index) {
        // check ranges
        setValidity(
            scope.ranges[index].low,
            scope.ranges[index].up,
            scope.lineForm.up);
    }
 
    function setValidity(low, up, element) {
        if(low && up && !!+low && !!+up) {
            element.$setValidity('range', 
                parseInt(low) < parseInt(up));
        }
    }
}]);

Here is the working fiddle http://jsfiddle.net/82PX4/2/

Let’s little bit improve UX and add “auto filling” (I mean to set next Up value to previous Low value +1, and vice versa).
So, here is the complete solution – http://jsfiddle.net/82PX4/3/

I’m not sure that this solution is good enough, so if you know better one, please send me a mail (gmail [the doggy] Mikita Manko [dot] com)

Social Share Toolbar

My slides from UxPeople.by

My slides from http://uxpeople.by/http://www.slideshare.net/mikitamanko/mikita-manko-at-u-xpeopleby

Social Share Toolbar

Little Snake on JavaScript (30 lines of code)

Inspired by Excel in 30 lines I challenged myself to create something fun in 30 lines of code. I choose the game Snake. After almost two hours of “home made” hackathone I created this Fiddle

That’s not pretty good code, but challenge was resolved without usage of any library:)

Some details:

  • I removed code for generation of markup
  • There is no any array, the algorithm is “DOM addicted” – I mark with class “s” snake’s cells and with “f” class cells with Food (So it’s obvious how to detect collision with snake or with food).
  • Every new snake’s cell will be marked with “data-n” attribute (incrementing integer value)
  • little hack how to remove unnecessary tale cell – find all divs with class “s” (all snake’s calls) then find div with minimal “data-n” value (it’s the oldest cell – the tale of the snake) and make this cell usual (remove the “s” class – remove the tale)
(function(width, height, length, current, dx, dy, x, y, hasFood, newEl){     
 
document.body.onkeydown = function(e){
    dx = (e.keyCode - 38) % 2, dy = (e.keyCode - 39) % 2;
};
 
var timer = setInterval(function () {
    x = (x + dx) < 0 ? width - 1 : (x + dx) % width; 
    y = (y + dy) < 0 ? height - 1 : (y + dy) % height;
    newEl = document.getElementsByClassName(y + '_' + x)[0]
    if(newEl.className.indexOf('s') > 0) {
    	clearInterval(timer), alert('Game Over! Score: ' + length)
    };
    if(newEl.className.indexOf('f') > 0) {
    	newEl.className = newEl.className.replace(' f', ''), length++, hasFood = false;
    }
    newEl.className += ' s', newEl.setAttribute('data-n', current++);
 
    for(var i = 0, min = Infinity, item, items = document.getElementsByClassName('s'), len = items.length; i < len && len > length; i++)
    	if(+items[i].getAttribute('data-n') < min)
    		min = +items[i].getAttribute('data-n'), item = items[i];
 
    if(!!item) item.className = item.className.replace(' s', '');
 
    for(var fItem, fX, fY; !hasFood; fX = Math.round(Math.random() * 10 % width), fY = Math.round(Math.random() * 10 % height))
    	if(!!fX && !!fY && document.getElementsByClassName(fY + '_' + fX)[0].className.indexOf('s') < 0)
    		hasFood = true, document.getElementsByClassName(fY + '_' + fX)[0].className += ' f';
}, 1000);
 
})(10, 10, 5, 1, 1, 0, 0, 0, false, null);

P.S. code is not good enough even for hackathone but seems that it works fine. Also it’s only 30 lines of code.

Social Share Toolbar

Chrome remote debugging on ios 6 (iphone 5)

Let’s dig into chrome remote debugging on ios 6 (iphone 5)

1. First of all we need to configure fiddler as a proxy
open fiddler->tools->fiddler options-> connections: set port to 8888, check “allow remote computers to connect” (google how to set up fiddler proxy for details)

2. configure iphone:
open command line on your desktop and type “ipconfig” to figure out your local ip
open settigns on iphone->wi-fi->current active network settings: scroll down, set proxy to manual and fill in proxy ip (ip from previous point and port 8888)

3. check that iphone is using your desktop as a proxy
just open any page in chrome on iphone and check fiddler out

4. install nodejs (click install here http://nodejs.org/)
check installation with the following commands:

node -v

I got something like – “v0.10.17”
then check that npm has been also installed

npm -v

I got something like 0 “1.3.8”

5. configure local server for files
e.g. we can add to hosts file “127.0.0.1 mydomain.com”
and set up new IIS site for this domain, so we got this page “http://mydomain.com/mypage.html” mapped on local env

6. configure local page – we need to add this script to our local page

<script src="http://mydomain:8080/target/target-script-min.js#anonymous"></script>

7. run weinre – run from console

weinre --boundHost mydomain.com

should respond something like “2013-08-26T13:31:58.109Z weinre: starting server at http://mydomain.com:8080”

8. open debugger
open this link http://mydomain.com:8080/client/#anonymous

9. open page on a mobile device – open page with weinre script – http://mydomain.com/mypage.html

10. check out desktop chrome with opened http://mydomain.com:8080/client/#anonymous – this debugger should be attached to the page opened on iphone

11. Thats all

P.S.
some helpful links:
http://people.apache.org/~pmuellr/weinre/docs/latest/Installing.html
http://people.apache.org/~pmuellr/weinre/docs/latest/Running.html

Social Share Toolbar

Mongoose aggregate with $group by nested field

Let’s say we have a collection in mongodb with document’s structure similar to this:

// schema on nodejs via mongoose:
var mongoose = require('mongoose')
    , Schema = mongoose.Schema
    , ObjectId = Schema.ObjectId;
 
var flatSchema = new Schema({
    _id: ObjectId,
    rentType: Number,
    price: Number,
    thumbnail: String,
    address: {
        country: String,
        city: String,
        street: String,
        district: Number
        // ... more fields
    }
    // ... some more fields
}, { collection: 'flats'});
 
module.exports = mongoose.model('Flat', flatSchema);

and we need to get some items (that matched to some request) and then group all documents by “address.district” (nested field) and after this we also need to collect some stats – e.g. min and max price and flats count for each district.

Let’s do it with usage of Mongoose:

    // e.g. let's define request
    var rules = [{'address.city': '1'}, {price: {$gte: 200}}];
 
    // and here are the grouping request:
    Flat.aggregate([
        { $match: {$and: rules } },
        {
            $project: {
                _id: 0, // let's remove bson id's from request's result
                price: 1, // we need this field
                district: '$address.district' // and let's turn the nested field into usual field (usual renaming)
            }
        },
        {
            $group: {
                _id: '$district', // grouping key - group by field district
                minPrice: { $min: '$price'}, // we need some stats for each group (for each district)
                maxPrice: { $max: '$price'},
                flatsCount: { $sum: 1 }
            }
        }
    ], {}, callback);

First of all – it’s not possible to group by nested field, e.g. “address.district”, thats why we workarounded this by field’s renaming in $project.

Here is the list of interesting reading:
http://docs.mongodb.org/manual/reference/aggregation/group/
http://docs.mongodb.org/manual/reference/aggregation/project/
http://mongoosejs.com/docs/api.html#model_Model.aggregate

That was simple example of how to group something via mongoose on nodejs without usage of direct access to mongo’s db.collection.

Social Share Toolbar

Slides from my talk at The Rolling Scopes #2

My slides http://www.slideshare.net/mikitamanko/developing-of-a-high-load-java-script-framework from The Rolling Scopes #2

Social Share Toolbar

My talk at Mobile Optimized conference

Last friday, I talked about Kendo UI at Mobile Optimized conference in webnotbombs.net section.

My slides http://www.slideshare.net/mikitamanko/kendo-ui-mikita-manko-at-mobile-optimized

Social Share Toolbar

jQuery slider for horizontal images scrolling

This is totally haphazard post, was created during the absolute idleness.
I believe that everyone saw this “trick” many times – horisontal slider scrolls horizontal line of images.
Here is simple markup

<div class="scrollableContainer">
    <div class="content">
        <div class="scrollableContent">
            <div class="item">1</div>
            <div class="item">2</div>
            <div class="item">3</div>
            <div class="item">4</div>
            <div class="item">5</div>
            <div class="item">6</div>
            <div class="item">7</div>
            <div class="item">8</div>
            <div class="item">9</div>
            <div class="item">10</div>
        </div>
    </div>
    <div class="slider"></div>
</div>
<div class='console'></div>

we gonna use numbers instead of images (imagine that these numbers are images for the poor)

some styles:

.scrollableContainer {
    width: 500px;
    height: 200px;
}
.scrollableContainer .content {
    border: 2px solid #999;
    border-radius: 5px;
    height: 100px;
    width: 500px;
    overflow: hidden;
}
.scrollableContainer .scrollableContent {
    font-size: 30px;
    margin-left: 0px;
    width: 1000px;
}
.scrollableContainer .scrollableContent .item {
    font-size: 50px;
    font-weight: bold;
    border: 5px solid grey;
    border-radius: 5px;
    width: 70px;
    height: 70px;
    margin: 10px;
    text-align: center;
    float: left;
}
.scrollableContainer .slider {
    width: 300px;
    margin-top: 20px;
    margin-left: auto;
    margin-right: auto;
}
 
/* overridings for jquery UI styles: */
.scrollableContainer .slider .ui-slider-handle.ui-state-default.ui-corner-all {
    border: 4px solid #999;
    border-radius: 50%;
    cursor: pointer;
}
 
.scrollableContainer .slider.ui-slider.ui-slider-horizontal.ui-widget.ui-widget-content {
    background: #ddd;
}

and here is the magic:

$(function() {
    $( ".slider" ).slider({
        slide: function( event, ui ) {
            $('.console').text(ui.value);
            $('.scrollableContent').css('margin-left', -ui.value * 4);
        }
    });
});

How it works – div with width 1000 px is inside of the div with smaller fixed width and “overflow: hidden”, and we just need to change “margin-left: -X” of the internal div depending on the slider’s position.

Here is my fiddle http://jsfiddle.net/TGEQf/

Social Share Toolbar

How to clone an object in JavaScript

What is the most efficient way to clone an object in JavaScript?

Here is the wrong way that will not work properly

var obj1 = { a: 1, b: { c: {d: 2}, e: 3}},
	obj2 = {};
 
console.dir(obj1);
 
for(var key in obj1) {
	if(obj1.hasOwnProperty(key)) {
		obj2[key] = obj1[key];
	}
}
 
console.dir(obj2); // seems the same object, but NOT

obj2 will look the same as an obj1, but it’s not a clone (because we just copied references), let’s prove that:

var obj1 = { a: 1, b: { c: {d: 2}, e: 3}},
	obj2 = {};
 
for(var key in obj1) {
	if(obj1.hasOwnProperty(key)) {
		obj2[key] = obj1[key];
	}
}
 
obj1.b.c.d = 42; // Changed obj1
 
console.dir(obj2); // OOPS, changes of obj1 has been applied to obj2

So it’s not a clone.

Here is the most efficient “handmade” way to clone the object:

var obj1 = { a: 1, b: { c: {d: 2}, e: 3}},
	obj2 = {};
 
obj2 = JSON.parse(JSON.stringify(obj1));
 
obj1.b.c.d = 42; // Changed obj1
 
console.log(obj1);
console.dir(obj2); // cool, obj2 still the same

BTW, in production code it’s recommended to use this way:

var obj1 = { a: 1, b: { c: {d: 2}, e: 3}},
	obj2 = {};
 
obj2 = $.extend(true, {}, obj1); // jQuery extend method
 
obj1.b.c.d = 42; // Changed obj1
 
console.log(obj1);
console.dir(obj2); // cool, obj2 still the same

I think that it is the best way to clone the object (described above)
Here is the list of all possible ways to clone the object:

// jQuery deep copy:
var newObject = jQuery.extend(true, {}, oldObject); // 76% slower
// JSON
var newObject = JSON.parse(JSON.stringify(oldObject)); // 12% slower
// jQuery copy (not deep)
var newObject = jQuery.extend({}, oldObject); // 24% slower
// simple clone function
var newObject = clone(oldObject); // fastest way, first function, that will copy only references (seems to be completely wrong way)
// ES5 Object.clone
var newObject = Object.clone(oldObject); // 85% slower

Here is the link on benchmark http://jsperf.com/cloning-an-object/2

So, the best production way to clone the object is still jQuery deep copy (not due to the benchmarks, but it’s the most safe and recommended way)
Let’s dig into this, and take a look under the hood. Here it the source code of jQuery extend:
https://github.com/jquery/jquery/blob/master/src/core.js#L265

Here is the “core” part

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];
 
				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}
 
				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];
 
					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}
 
					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );
 
				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

So, let’s take a look at fully “handmade” function “clone”
Here is simplified similar function (but not the same)

function extend (target, other) {
  target = target || {};
  for (var prop in other) {
    if (typeof source[prop] === 'object') {
      target[prop] = extend(target[prop], source[prop]);
    } else {
      target[prop] = source[prop];
    }
  }
  return target;
}

P.S.
somewhere I saw this way to clone the object

eval(uneval(o));

Please, do not even think about using of this. Eval is completely evil (Douglas Crockford will help you to find out the reason)

Social Share Toolbar

How to use mongoose with MongoDB in Node.js

Here is the example how to start simple project with usage of Mongoose for MongoDB on Node.js and mongoDB hosted on mongolab.

First of all you need to correct package.json file in your project to something like this

{
    "name": "rent",
    "version": "0.0.1",
    "private": true,
    "scripts": {
        "start": "node app"
    },
    "dependencies": {
        "express": "3.0.2",
        "jade": "*",
        "log4js": "0.5.6",
        "mongoose" : "3.5.4",
        "moment" : "1.7.2",
        "mongoose-pureautoinc": "*"
    }
}

Make sure that you have added mongoose package, other stuff just for example
run
Mikitas-MacBook-Air-2:test nik$ sudo npm install
or just “npm install” (for windows or if you configured permissions)
You can also use “npm install mongoose” to install it.

Let’s create mongoDB “model”. So somewhere in your DAL try this code:

var mongoose = require('mongoose')
    , Schema = mongoose.Schema
    , ObjectId = Schema.ObjectId;
 
// let's create scheme for some random stuff:
var postSchema = new Schema({
    _id: ObjectId,
    thumbnail: String,
    whenCreated: Date,
    whenUpdated: Date,
    comments: [{
            author: {
                authorId: ObjectId, 
                name: String, 
                thumbnail: String
            }, 
            date: String, 
            text: String
    }],
    rating: Number,
    isApproved: Boolean,
    text: String
}, { collection: 'post'});
 
// and let's register this scheme as a model and make global for this module
module.exports = mongoose.model('Post', postSchema);

Now we can include this file:

var Post = require('../DAL/models/Post');

And use it for example to get all items from collections

exports.getPosts = function (callback){
	return Post.find({/* query */}, function (err, docs){
		if (!err) { 
			callback(docs);
		}
		else { 
			throw err;
		}
	});
}

Note: that the operation is async and you will get result only in callback
please check this mongo docs out

Or we can insert new item into collection:

exports.addPost = function (){
    var post = new Post();
    post.rating = 5.89;
    post.text = "Here is one more post to my blog on MikitaManko.com/blog/"; 
    post.isApproved = true;
    // other fields
    post.save();
}

Here is one more link on mongo docs

Finally, to use this “DAL” methods we need to create connection to DB.
You can use https://mongolab.com/ service to host your DB for free (until your DB is less then 500 mb).
After you created new BD on mongolab you can create connection with the following way:

var mongoose = require('mongoose');
 
mongoose.connect('mongodb://<your login>:<your password>@<your id>.mongolab.com:61777/<database name>');
// here you can call methods described above

That’s it, just several lines of code, some clicks and you have MondoDB database already hosted in web.
Perfect solution for Hackathons, fast tests and startups.

By the way, you can host you Node.js code on https://www.heroku.com/ service also for free (until it exceeds some limitation, for details check heroku website out)

Social Share Toolbar