Resizing input size on focus and blur in angularjs - angularjs

I'm trying to change the input size from 20 to 100 each time it ng-focus. Then, when it ng-blur, it should convert from 100 to 20. Here is my html code:
<div ng-controller="MenuCtrl" class="navbar-collapse collapse bs-navbar-collapse navbar-ex1-collapse">
<input type="text" class="form-control" ng-blur="{[{ change_text_width() }]}" ng-focus="{[{ change_text_width() }]}" size="{[{search_text_width}]}" placeholder="Search">
</div>
Before my angular module, I put a parent module:
var JobMaps = angular.module('JobMaps', ['MenuCtrl']);
Here is my angular code:
var MenuCtrl = angular.module('MenuCtrl',[]);
MenuCtrl.controller = angular.module('MenuCtrl',function($scope) {
$scope.search_text_width = 20;
$scope.change_text_width = function() {
if ($scope.search_text_width==20) {
$scope.search_text_width = 100;
//alert($scope.search_text_width);
} else {
$scope.search_text_width = 20;
//alert($scope.search_text_width);
}
}
});
Observation:
I tried to alert it, and it seems like it runs several times.
I think it may be converting from 20 to 100 and 100 to 20 back so fast, I could not see it... Well, it could be a possibility because of several alerts that popup.

In the Angular world you use directives for DOM manipulation. One advantage is that you can reuse the directives without much extra code and seperate the UI stuff from the controller. I created a plunker for the resizing input: Plunker

I changed the attribute size because width can affect the responsiveness when using Bootstrap.
MenuCtrl.directive('changeSize', function () {
return {
restrict: 'A',
link: function($scope, $element, $attributes) {
var onFocusSize = $attributes['changeSize'] || $element.attr('size');
var onBlurSize = $element.attr('size');
$element.focus(function() { $element.attr('size',onFocusSize); });
$element.blur(function() { $element.attr('size',onBlurSize); });
}
}
});

Related

AngularJs views and the DOM (Leaflet and ag-grid)

I have an AngualrJS app which is currently single page. It will display either a Leaflet map or two Ag-grid, using ng-show/hide on a boolean value, to show only map or grids at a time.
I was thinking that it would be better to add routing, using ui-router, and have 2 views, one for the Leaflet map & one for the two ag-grid.
I had some problems with the grids, probably because it is necessary to do something like
// wait for the document to be loaded, otherwise
// ag-Grid will not find the div in the document.
document.addEventListener("DOMContentLoaded", function() {
// lookup the container we want the Grid to use
var eGridDiv = document.querySelector('#myGrid');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(eGridDiv, gridOptions);
I am not asking to solve my coding problem, which I hope to figure out by myself.
Instead, I am asking you to help me understand how AngularJs ui-router views work.
Are they always bound to the DOM, and hidden until the appropriate state is entered, or are they added to/removed from the DOM as the stata changes?
Is there anything else that I need to know, in order to understand how it works under the hood?
If I understand the requirements correctly, you could first define some conditions and then make the transition to the appropriate view.
In the example code, you can change the checked attribute for inputs for changing view displayed.
var myApp = angular.module('helloworld', ['ui.router'])
.config(function($stateProvider) {
var helloState = {
name: 'hello',
url: '/hello',
template: '<h3>hello world!</h3>'
}
var aboutState = {
name: 'about',
url: '/about',
template: '<h3>Its the UI-Router hello world app!</h3>'
}
$stateProvider.state(helloState);
$stateProvider.state(aboutState);
})
.directive('selectView', selectView);
let state;
let transitions;
function selectView($state, $transitions) {
state = $state;
transitions = $transitions;
return {
restrict: 'A',
link: selectViewLinkFn
}
}
function selectViewLinkFn($scope, $element) {
const triggers = document.querySelectorAll('input[type="radio"]');
transitions.onSuccess({}, () => {
console.log('onSuccess: ', document.querySelector('h3').innerHTML);
});
transitions.onStart({}, () => {
const findedInView = document.querySelector('h3');
console.log(`onStart: ${findedInView ? findedInView.innerHTML : 'nothing found'}`);
});
setView($scope);
for (const trigger of triggers) {
trigger.addEventListener('change', () => setView($scope, true))
}
function setView(scope, needDigest) {
// Check some conditions
let currentRoute;
for (const trigger of triggers) {
if (trigger.checked) {
currentRoute = trigger.value;
}
}
state.go(currentRoute);
if (needDigest) {
scope.$digest();
}
}
}
selectView.$inject = ['$state', '$transitions'];
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<script src="//unpkg.com/#uirouter/angularjs/release/angular-ui-router.min.js"></script>
<body ng-app="helloworld" select-view>
<label for id="selectHello">Hello</label>
<input name="selectorForView" type="radio" id="selectHello" value="hello" checked>
<label for id="selectAbout">About</label>
<input name="selectorForView" type="radio" id="selectAbout" value="about">
<h2 ng-bind="selectorForView">
<h2>
<ui-view></ui-view>
</body>

Angular directive to detect scrolling within an ng-repeat container

In my Angular app, I've created a simple directive to do something on scroll, based on the answer provided to a similar question here.
Here is the code to my onScroll directive:
(function() {
'use strict';
angular
.module('proverbial')
.directive('onScroll', Directive);
Directive.$inject = [ '$document' ];
function Directive($document) {
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
element.bind('scroll', function(e) {
console.warn(e)
if (element[ 0 ].scrollTop + element[ 0 ].offsetHeight >= element[ 0 ].scrollHeight) {
scope.$apply(attr.onScroll);
}
});
}
}
})();
This is to be used in the following HTML template:
<div class="absolute" on-scroll="vm.totalDisplayed += 100">
<span class="card hover"
ng-repeat="proverb in vm.proverbs | limitTo: vm.totalDisplayed">
<a>{{proverb.text | maxLength: 90}}</a>
</span>
</div>
The intention is to keep increasing vm.totalDisplayed on page scroll, to create an effect similar to lazy-loading.
However, when I scroll down to the end of what has been already loaded with ng-repeat, nothing happens. The console.warn(e) never triggers.
On the browser console I can see that the containing <div class="absolute" on-scroll="vm.totalDisplayed += 100"> does indeed scroll up past the browser's window.
What's missing?
You have a minor mistake in your code:
if (element[ 0 ].scrollTop + element[ 0 ].offsetHeight >= element[ 0 ].scrollHeight) {
scope.$apply(attr.onScroll);
}
attr is undefined. Should be attrs.
Furthermore, I can't reproduce the problem. I've created a Plunker from your code and the warning shows just fine. Only thing what I find weird is that I couldn't get vm.totalDisplayed += 100 to work. vm.totalDisplayed = vm.totalDisplayed + 100 worked though.
http://plnkr.co/edit/ooMFG4ZAZzxJf9zuChyA
Despite #Tom pointing out correctly that I had a syntatic error in my directive, it turns out that that wasn't the issue.
The issue was that the element to which I attached my directive was not actually being scrolled due to its CSS styling, so I had to tweak the code slightly, by binding scroll to $document instead:
( function () {
'use strict';
var angular = window.angular || {};
angular
.module( 'proverbial' )
.directive( 'onDocumentScroll', Directive );
Directive.$inject = ['$document'];
function Directive( $document ) {
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link( scope, element, attrs ) {
$document.bind( 'scroll', function () {
if ( $document.scrollTop() - scope.vm.latestThreshold >= element[0].offsetHeight ) {
scope.vm.latestThreshold += $document.scrollTop();
scope.$apply( attrs.onDocumentScroll );
}
} );
}
}
} )();
This solved my problem.

Attaching/displaying images in a form using AngularJS

Currently learning AngularJS, and I want to use it to upload and display images as part of a way to document project summaries. I found Angular File Upload, but I'm not exactly too sure as to how I should go about implementing it in my program.
Basically what I have so far is a button that generates a form, and clicking the submit adds those fields to a display that generates upon the button click. The text/number fields display fine, but obviously attaching/uploading images would require more work.
Demo picture
I'm using a C9 development environment so I'm not too sure how to make my workspace publicly viewable, but I pasted the main code segments on JSFiddle (it doesn't exactly work properly but at least you can view the JS code):
https://jsfiddle.net/edmond_wu/63v98qt6/9/
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope) {
$scope.global_savings = 0;
$scope.items = [];
$scope.itemsToAdd = [];
$scope.addReport = function(item) {
var index = $scope.itemsToAdd.indexOf(item);
$scope.global_savings += Number(item.savings);
if ($scope.global_savings >= 100000) {
alert("Your benchmark of $100,000 has been reached!");
}
$scope.itemsToAdd.splice(index, 1);
$scope.items.push(angular.copy(item));
}
$scope.addNew = function() {
$scope.itemsToAdd.push({
title: '',
manager: '',
savings: '',
summary: '',
result: '',
image: ''
});
}
The HTML is something like this with a Submit button at the bottom (the submit button should upload the image along with the rest of the form data):
<input type="file" class="button" ng-model="itemToAdd.image" accept="image/*">
<input type="submit" class="button" ng-click="addReport(itemToAdd)">
I don't think you can use ng-model on an input of type="file".
I managed to get it working using a custom directive from here:
app.directive("fileread", [function () {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var reader = new FileReader();
reader.onload = function (loadEvent) {
scope.$apply(function () {
scope.fileread = loadEvent.target.result;
});
}
reader.readAsDataURL(changeEvent.target.files[0]);
});
}
}
}]);
Then change your input to:
<input type="file" class="button" fileread="itemToAdd.image" accept="image/*">
All you have to do is add directive to your current angular code and change the one line in the html, thats it.
jsfidlle: https://jsfiddle.net/63v98qt6/10/
The above creates a directive fileread which binds the image to the itemToAdd.image of your object on scope.

AngularJS Template Replace directive not working

I have a templating directive that is almost working the way I want, simple version works :-
<div bw-template-replace>
<template>This is template text, replace [[#this]] please</template>
<this>text replaced</this>
</div>
expands to
<div bw-template-replace><span>This is template text, replace text replaced please</span></div>
However, if I embed other directives they dont fully work as expected.
See my plunk http://plnkr.co/edit/dLUU2CMtuN5WMZlEScQi?p=preview
At the end of the directive's link function I $compile the resulting text/node which works for {{scope interpolated text}} but does not work for embedded directives using the same scope.
The reason I need this is because I am using ng-translate for an existing ng-app and I want to use the existing English text as keys for translation lookups. An uncommon translation case is where we have HTML like the following (extract from the app), the [[#ageInput]] and [[#agePeriod]] 'arguments' may appear at different places in other languages, as I understand it ng-translate has no real support for this scenario currently.
<div class="row-fluid">
<div class="span12" translate bw-template-replace>
<template>
If current version of media has not been read for [[#ageInput]] [[#agePeriod]]
</template>
<ageInput><input type=number ng-model="policy.age" style="width:50px"/></ageInput>
<agePeriod><select style="width:100px" ng-model="policy.period" ng-options="p for p in periods" /></agePeriod>
</div>
</div>
Any help much appreciated.
I love having to work through these scenarios when you are a newbie at something as it really forces you to understand what is happening. I now have it working, basically my previous directive I found several ways of just replacing the html in the hope Angular would magically sort everything out. Now I have a better understanding of transclusion and in particular the transclusion function I made it work as desired. The cloned element passed into $transcludeFn already has scope attached and has been $compiled, so my function now parses the template text and generates individual textElement's and moves the argument elements around to suit the template.
My Current Solution
.directive('TemplateReplace', ['$compile', '$document', '$timeout',
function ($compile, $document, $timeout) {
return {
restrict: 'AC',
transclude: true,
link: function (scope, iElement, iAttrs, controller, transclude) {
transclude(scope, function (clone, $scope) {
$timeout(function () {
// Our template is the first real child element (nodeType 1)
var template = null;
for (var i = 0, ii = clone.length; i < ii; i++) {
if (clone[i].nodeType == 1) {
template = angular.element(clone[i]);
break;
}
}
// Remember the template's text, then transclude it and empty its contents
var html = angular.copy(template.text());
iElement.append(template); // Transcluding keeps external directives intact
template.empty(); // We can populate its inards from scratch
// Split the html into pieces seperated by [[#tagname]] parts
if (html) {
var htmlLen = html.length;
var textStart = 0;
while (textStart < htmlLen) {
var tagName = null,
tagEnd = htmlLen,
textEnd = htmlLen;
var tagStart = html.indexOf("[[#", textStart);
if (tagStart >= 0) {
tagEnd = html.indexOf("]]", tagStart);
if (tagEnd >= 0) {
tagName = html.substr(tagStart + 3, tagEnd - tagStart - 3);
tagEnd += 2;
textEnd = tagStart;
}
}
// Text parts have to be created, $compiled and appended
var text = html.substr(textStart, textEnd - textStart);
if (text.length) {
var textNode = $document[0].createTextNode(text);
template.append($compile(textNode)($scope));
}
// Tag parts are located in the clone then transclude appended to keep external directives intact (note each tagNode can only be referenced once)
if (tagName && tagName.length) {
var tagNode = clone.filter(tagName);
if (tagNode.length) {
template.append(tagNode);
}
}
textStart = tagEnd;
}
}
}, 0);
});
}
};
}
]);
If I have understood your question properly, I guess the issue is your directive is getting executed before ng-repeat prepares dom.So you need to do DOM manipulation in $timeout.
Check this plunker for working example.
Here is a nice explanation of similar problem: https://stackoverflow.com/a/24638881/3292746

How to conditionally insert HTML after AngularJs Directive paired with ngRepeat?

=================
First of I just want to say that I'm new to this AngularJs business (started earlier today) and I probably over complicate things.
With that being said let's get down to business, shall we?
I have the following jsFiddle and the html output is as follows.
<div ng-app="test" ng-controller="About" class="hex-grid clearfix ng-scope">
<div class="hex odd" ng-repeat="person in ledning">
<a href="">
<div class="inner">
<h3 class="ng-binding">Just a name</h3>
<hr class="grid-1 center">
<p class="ng-binding">a title</p>
</div>
<div class="hex-1">
<span class="after"></span>
</div>
<div class="hex-2">
<span class="after"></span>
</div>
<span class="after"></span>
</a>
</div>
<!-- and 5 times more -->
</div>
Now, What I want to achieve is this -->http://jsfiddle.net/engstrumpan/yCv79/4/embedded/result/.
This is just plain html (no AngularJs) just to demonstrate what I want to achieve. This particular layout was achieved by inserting <br /> after hexagon 3 and 5
Imagine now if I want a layout like so
1 1 1 1
1 1 1 1 or even 1 1
1 1 1 1 1 1
1 1 1 1
1 1 1
1 1
1
How would one go about implementing this?
This directive is used multiple times so I want it to be as generic as possible.
What I've tried so far is is the following
var app = angular.module('test', []);
app.controller('About', function ($scope) {
$scope.ledning = [
{
...
shouldBreak: true
}
});
app.directive('hexagon', function () {
var tmpl = '<div>
<!-- directive template snippet -->
</div>
<br ng-show="{{data.shouldBreak}}" />';
// rest of code
});
This <br ng-show="{{data.shouldBreak}}" /> won't work since AngularJs throws an exception with the following message Template must have exactly one root element.
Also messing with compile on the directive like so but that results only in the <br /> being inserted after the very last iteration (after the repeater is done working)
var app = angular.module('test', []);
app.directive('hexagon', function () {
return {
...
compile: function($scope, el, attrs){
return function ($scope, el, attrs){
if($scope.data.shouldBreak !== undefined){
el.after('<br />');
}
};
}
}
});
I've read the documentation when it comes to directives and either I'm just stupid or I'm missing the obvious so please help me out.
End of walloftext and maybe some sleep will do the trick. Jump back on the horse tomorrow so to speak.
So I managed to come to a solution after being away for a couple of days and then when I returned to town I tackled my problem again.
The solution
The view
<hexagon ng-repeat="person in ledning" data="person" layout="layout"></hexagon>
The controller:
var app = angular.module('test', []);
app.controller('About', function ($scope) {
...
$scope.layout = [2,4]; // what index (inside ngRepeat) should we break on
}
});
and the directive
app.directive('hexagon', function ($timeout) {
...
return {
...
scope: {
data: '=',
layout: '=' // grab the data bound to 'layout'
},
link: function ($scope, el, attrs) {
$timeout(function(){
for (var i = 0; i < $scope.layout.length; i++) {
if ($scope.layout[i] == $scope.$parent.$index)
el.after('<br />');
}
}, 0);
}
};
});
A working example can be seen here http://jsfiddle.net/engstrumpan/yCv79/5/.
So if I for instance were to work with 10 items I could set $scope.layout = [2,6] to get the following output in this jsFiddle
So how did I end up with this solution? Well I have to thank this guy (Lorenz Merdian) and his blog for it. At least when it comes down tho the use of $timeout to give the browser enough time to render the DOM for the element so I could insert my <br />
It might not be the most elegant solution but it works for me in this particular scenario
You could use pseudoclasses to insert a carriage return "\a" either before :before or after :after the element in question (although if you are supporting legacy browsers, this ain't for you).
CSS:
.last:before {
content: "\A";
white-space:pre;
}
And a conditional ng-class to set the line-breaking class, where item.lineBreak contained true or false depending on its order in the group:
ng-class="{last:item.lineBreak}"
That is the basic answer, and here's the working plunk with the ng-class on the element.
Fork of the plunk with the ng-class on the root element in the template. Same result.
..
..
The tricky/fun part is being able to dynamically insert that class/pseudoclass into the correct elements without hardcoding them. It looked like you wanted a pyramid (Fibonacci wannabe) design (which is what looked interesting about this question).
What I did was use a pretty basic looping process to insert the true property - (from the above example item.lineBreak) - into the items which were the 1st, 3rd, 6th, 10th, (and on) items in the ng-repeat. I put this in a $watch of a select-box model so you could choose the number of items in the group.
Here's the $watch and
var findFib = function(index){
if(index==increment){
rowLength += 1;
increment = rowLength + increment;
return true;
}
return false;
}
$scope.$watch('collectionAmount.value', function(val){
$scope.collection = [];
rowLength = 1;
increment = 1;
var i = 1;
//loop through the items, using the Fibonacci-like
//method to determine which items will recieve the
//lineBreak property of true;
while(i<=val){
var f = findFib(i);
$scope.collection.push({number: i, lineBreak: f});
console.log($scope.collection[i-1]);
i++;
}
//reverse the array if using :before pseudoclass to get upside-down pyramid:
$scope.collection.reverse();
})
From there, the ng-class conditionally applied the .last class which contained the :before pseudoclass that inserted the return. This could easily be scaled to fit your more complex markup.

Resources