I am new to AngularJS and I am working on implementing rich texts for comments, bold, italic etc. I came across angular-smiles which is quiet easy and amazing to use.
<body ng-controller="SomeCtrl">
<p ng-bind-html="message | smilies "></p>
<div class="input-group">
<span class="input-group-addon"
smilies-selector="message"
smilies-placement="right"
smilies-title="Smilies"></span>
<textarea ng-model="message" focus-on-change="message" class="form-control"></textarea>
</div>
I also have to implement other features like ** to bold and keeping line breaks as <br> . Can someone please guide me on the angular way of doing it. A small hint with relevant topic is good enough to help me out as the project in hand has very limited time to complete.
Your best bet will be filters. I would highly recommend you to go through it.
now, just to help you out, I have written down a small code to help you grab it better.
app.controller('MainCtrl', function($scope,FILTER_FUNCTIONS) {
$scope.message = '';
// remove this,just to show you line breaks
$scope.$watch('message',function(newVal,oldVal){
console.log(newVal)
})
});
function f1(str){
var obj = { expr: /\n+?/g, value: '<br>' };
return str.replace(obj.expr, obj.value)
};
function f2(str){
var boldArray = str.split("**");
console.log(boldArray)
if(boldArray.length < 3){
return str;
}else{
for(var count = 0 ; count < boldArray.length ; count++){
if(count%2 !== 0 && (count+1 !== boldArray.length)){
boldArray[count] = '<strong>'+boldArray[count]+'</strong>'
}else if(count%2 !== 0){
boldArray[count] = '**'+boldArray[count]
}
}
return boldArray.join("");
}
}
var arr = [f1,f2];
app.value('FILTER_FUNCTIONS',arr);
app.filter('richText', function(FILTER_FUNCTIONS) {
return function(string) {
return FILTER_FUNCTIONS.reduce(function(result, someFn) {
return someFn(result);
}, string || '');
};
});
and in your html , add message | smilies | richText.
you can increase the rich text feature just by adding new functions in your array arr
Related
I have a list of <md-checkbox> where it is repeated using JSON array.
I want to give an initial selection for 2 checkboxes. Please help me on this.
My js code:
$scope.elecselected = [];
$scope.toggle = function (elecitem, eleclist) {
var idx = eleclist.indexOf(elecitem);
if (idx > -1) {
eleclist.splice(idx, 1);
}
else {
eleclist.push(elecitem);
}
console.log("item:"+elecitem+ " List:"+ eleclist);
};
$scope.exists = function (elecitem, eleclist) {
return eleclist.indexOf(elecitem) > -1;
};
$scope.electrical = [{"ID":"161","Value":"ABC","Name":"AAAA BBB CCC","IsDefault":"FALSE"},{"ID":"162","Value":"CDE","Name":"CCC DDD EEE","IsDefault":"Y"},{"ID":"163","Value":"EFG","Name":"EEE FFF GGG","IsDefault":"FALSE"}];
HTML Code:
<div layout="row" style="margin-left: 5px;font-size: x-small;" ng-repeat="attribute in electrical">
<md-checkbox ng-checked="exists(attribute.ID, elecselected)" ng-click="toggle(attribute.ID, elecselected)">
<label name="{{value.ID}}">{{attribute.Name}}</label>
</md-checkbox>
</div>
I had tested with normal array values as $scope.elecitems = [1,2,3,4,5];
$scope.elecselected = [2,4]; and this worked but while giving JSON array it is not working. Please help...
Thanks #DincaAdrian for the idea of giving attribute.IsDefault === "Y" in ng-checked. Also I had added codes to maintain the selection procedure.
Now its working fine. Some logic must be added on top of this code to get the selected values.
I want to make some kind of project list from a JSON file. The data structure (year, month, project) looks like this:
[{
"name": "2013",
"months": [{
"name": "May 2013",
"projects": [{
"name": "2013-05-09 Project A"
}, {
"name": "2013-05-14 Project B"
}, { ... }]
}, { ... }]
}, { ... }]
I'm displaying all data using a nested ng-repeat and make it searchable by a filter bound to the query from an input box.
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.projects | filter:query | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
If I type "Project B" now, all the empty parent elements are still visible. How can I hide them? I tried some ng-show tricks, but the main problem seems so be, that I don't have access to any information about the parents filtered state.
Here is a fiddle to demonstrate my problem: http://jsfiddle.net/stekhn/y3ft0cwn/7/
You basically have to filter the months to only keep the ones having at least one filtered project, and you also have to filter the years to only keep those having at least one filtered month.
This can be easily achieved using the following code:
function MainCtrl($scope, $filter) {
$scope.query = '';
$scope.monthHasVisibleProject = function(month) {
return $filter('filter')(month.children, $scope.query).length > 0;
};
$scope.yearHasVisibleMonth = function(year) {
return $filter('filter')(year.children, $scope.monthHasVisibleProject).length > 0;
};
and in the view:
<div class="year" ng-repeat="year in data | filter:yearHasVisibleMonth | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:monthHasVisibleProject | orderBy:sortMonth:true">
This is quite inefficient though, since to know if a year is accepted, you filter all its months, and for each month, you filter all its projects. So, unless the performance is good enough for your amount of data, you should probably apply the same principle but by persisting the accepted/rejected state of each object (project, then month, then year) every time the query is modified.
I think that the best way to go is to implement a custom function in order to update a custom Array with the filtered data whenever the query changes. Like this:
$scope.query = '';
$scope.filteredData= angular.copy($scope.data);
$scope.updateFilteredData = function(newVal){
var filtered = angular.copy($scope.data);
filtered = filtered.map(function(year){
year.children=year.children.map(function(month){
month.children = $filter('filter')(month.children,newVal);
return month;
});
return year;
});
$scope.filteredData = filtered.filter(function(year){
year.children= year.children.filter(function(month){
return month.children.length>0;
});
return year.children.length>0;
});
}
And then your view will look like this:
<input type="search" ng-model="query" ng-change="updateFilteredData(query)"
placeholder="Search..." />
<div class="year" ng-repeat="year in filteredData | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Example
Why not a custom $filter for this?
Efficiency: the nature of the $diggest cycle would make it much less efficient. The only problem is that this solution won't be as easy to re-use as a custom $filter would. However, that custom $filter wouldn't be very reusable either, since its logic would be very dependent on this concrete data structure.
IE8 Support
If you need this to work on IE8 you will have to either use jQuery to replace the filter and map functions or to ensure that those functions are defined, like this:
(BTW: if you need IE8 support there is absolutely nothing wrong with using jQuery for these kind of things.)
filter:
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
}
Acknowledgement
I want to thank JB Nizet for his feedback.
For those who are interested: Yesterday I found another approach for solving this problem, which strikes me as rather inefficient. The functions gets called for every child again while typing the query. Not nearly as nice as Josep's solution.
function MainCtrl($scope) {
$scope.query = '';
$scope.searchString = function () {
return function (item) {
var string = JSON.stringify(item).toLowerCase();
var words = $scope.query.toLowerCase();
if (words) {
var filterBy = words.split(/\s+/);
if (!filterBy.length) {
return true;
}
} else {
return true;
}
return filterBy.every(function (word) {
var exists = string.indexOf(word);
if(exists !== -1){
return true;
}
});
};
};
};
And in the view:
<div class="year" ng-repeat="year in data | filter:searchString() | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:searchString() | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | filter:searchString() | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Here is the fiddle: http://jsfiddle.net/stekhn/stv55sxg/1/
Doesn't this work? Using a filtered variable and checking the length of it..
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true" ng-show="filtered.length != 0">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in filtered = (month.projects | filter:query) | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
I've got a list of events on a site I'm building and I would like the past events to be removed automatically. I've written a filter that is doing this, except it is removing today's events as well. I'm trying to use Angular Moment. Here is my code:
angular.module('zenCityApp')
.filter('filterPastDates', function (moment) {
return function (events) {
var filterByDate = [];
for (var i = 0; i < events.length; i++) {
var currentDate = new Date();
if(moment(currentDate).isBefore(events[i].date, 'hour')) {
console.log("we're in!");
filterByDate.push(events[i]);
console.log(filterByDate);
}
}
return filterByDate;
};
});
And here is the markup:
div ng-repeat="event in events | limitTo:100 | filter:tfilter | orderBy: 'date' | filterPastDates">
<div class="row">
<div class="col-md-6">
<h4>{{event.date | amDateFormat:'MMMM Do'}}</h4>
</div>
<div class="col-md-6">
<h4>{{event.name}}</h4>
</div>
</div>
Any help would be greatly appreciated.
What would be better is to create a cut-off moment for which you want to test. Moments api gives you a pretty easy way to do it by using .startOf('day'). That will give you a moment that represents today at 12:00am (the first second in the day). But since you also want to include that value in your filter, you can then subtract 1 millisecond from the value.
var cutOffDate = moment().startOf('day').subtract(1,'millisecond');
And now you can easily use that in your filter. Notice that I created that object outside of the loop (since it's not supposed to change), and I created it as a moment directly instead of creating a new moment object each time in the loop.
app.filter('filterPastDates', function () {
return function (events) {
if (events && events.length) {
var filtered = [];
var cutOffDate = moment().startOf('day').subtract(1,'millisecond');
for (var i = 0; i < events.length; i++) {
var evt = events[i];
if (cutOffDate.isBefore(evt.date)) {
filtered.push(evt);
}
}
return filtered;
} else {
return events;
}
};
});
Here's a sample plunker: http://plnkr.co/edit/kSXu0Z3J7zoyBMBjoE84?p=preview
looking for some ideas here. i have a meal plan object that contains an array of meals. only one meal can be set as primary at a time but i want the user to be able to cycle through the array of meals and mark a meal as primary. i am stuck trying to figure out if ngrepeat makes sense here or ngswitch or ngshow. any thoughts or samples would be highly appreciated!
I have tried multiple approaches with no luck.
thanks
You could cycle through the meals by index of the meal and have a button to choose the meal like this:
http://jsfiddle.net/c6RZK/
var app = angular.module('mealsApp',[]);
app.controller('MealsCtrl',function($scope) {
$scope.meals = [
{name:'Meatloaf'},
{name:'Tacos'},
{name:'Spaghetti'}
];
$scope.meal_index = 0;
$scope.meal = {};
$scope.next = function() {
if ($scope.meal_index >= $scope.meals.length -1) {
$scope.meal_index = 0;
}
else {
$scope.meal_index ++;
}
};
$scope.choose = function(meal) {
$scope.meal = meal;
}
});
HTML
<div ng-app="mealsApp" ng-controller="MealsCtrl">
<div ng-repeat="m in meals">
<div ng-if="meal_index == $index">
<strong>{{m.name}}</strong>
<button ng-click="choose(m)">Choose</button>
</div>
</div>
<hr>
<button ng-click="next()">Next</button>
<hr>Your Choice: {{meal.name}}
</div>
You could just attach a property to the plan, with a flag that says whether or not it's the primary plan.
Here's a sample implementation:
$scope.plans = [{name:"One"}, {name:"Two"}, {name:"Three"}];
$scope.selectPlan = function(plan) {
for(var i = 0, l = $scope.plans.length; i < l; i++) {
$scope.plans[i].primary = false;
if($scope.plans[i] === plan) {
$scope.plans[i].primary = true;
}
}
};
HTML:
<ul>
<li ng-click="selectPlan(plan)" ng-repeat="plan in plans" ng-class="{primary: plan.primary}"><a href>{{plan.name}}</a></li>
</ul>
If you'd rather not attach properties you could use something like a selected index property on your controller.
I'm adding a simple sort on a page. The idea is to search products. These products are written in spanish language and has accents. For example: 'Jamón'.
Here is my code:
<div class="form-inline">
<label>Search</label>
<input type="text" ng-model="q"/>
</div>
<div ng-repeat="product in products_filtered = (category.products | filter:q | orderBy:'name')">
....
</div>
The only problem i have is that you have to type in "Jamón" in order to find the product "Jamón". What I want is to be more flexible, if the user types in "Jamon", the results must include "Jamón".
How can i search with angular filters and forget about accents? Any Idea?
Thanks in advance.
You'll need to create a filter function (or a full filter). This is the simplest thing that could possibly work:
HTML
<div ng-app ng-controller="Ctrl">
<input type="text" ng-model="search">
<ul>
<li ng-repeat="name in names | filter:ignoreAccents">{{ name }}</li>
</ul>
</div>
Javascript
function Ctrl($scope) {
function removeAccents(value) {
return value
.replace(/á/g, 'a')
.replace(/é/g, 'e')
.replace(/í/g, 'i')
.replace(/ó/g, 'o')
.replace(/ú/g, 'u');
}
$scope.ignoreAccents = function(item) {
if (!$scope.search) return true;
var text = removeAccents(item.toLowerCase())
var search = removeAccents($scope.search.toLowerCase());
return text.indexOf(search) > -1;
};
$scope.names = ['Jamón', 'Andrés', 'Cristián', 'Fernán', 'Raúl', 'Agustín'];
};
jsFiddle here.
Please notice that this only works for arrays of strings. If you want to filter a list of objects (and search in every property of every object, like Angular does) you'll have to enhance the filter function. I think this example should get you started.
JavaScript has a neat function for this, called localeCompare, where you can specify sensitivity = base, an option that makes it treat á and a as equivalent, but it is not widely supported. I think your only option is to create a filter function wherein you normalise both strings manually (replacing ó with o and so on) and compare the results.
Here is a slightly enhanced version (not counting the extra foreign characters it searches for, of course) of the above. I put it straight into my controller and it allowed me to search all the data that I wanted to search. Thanks Bernardo for your input into this!
Hope it helps somebody out.
function removeAccents(value) {
return value
.replace(/á/g, 'a')
.replace(/â/g, 'a')
.replace(/é/g, 'e')
.replace(/è/g, 'e')
.replace(/ê/g, 'e')
.replace(/í/g, 'i')
.replace(/ï/g, 'i')
.replace(/ì/g, 'i')
.replace(/ó/g, 'o')
.replace(/ô/g, 'o')
.replace(/ú/g, 'u')
.replace(/ü/g, 'u')
.replace(/ç/g, 'c')
.replace(/ß/g, 's');
}
$scope.ignoreAccents = function (item) {
if ($scope.search) {
var search = removeAccents($scope.search).toLowerCase();
var find = removeAccents(item.name + ' ' + item.description+ ' ' + item.producer+ ' ' + item.region).toLowerCase();
return find.indexOf(search) > -1;
}
return true;
};
The method, proposed by #Michael Benford has a big disadvantage: it is a stateful filter. This practice is strongly discouraged by angular. Plus, the method doesn’t work with deep arrays and objects.
I prefer to extend the standard angular filter:
HTML:
<div ng-app ng-controller="Ctrl">
<input type="text" ng-model="search">
<ul>
<li ng-repeat="person in people | filter: search : searchFn">{{ person.names }} {{ person.surnames }}</li>
</ul>
</div>
Javascript:
function Ctrl($scope) {
$scope.people = [{names: 'Jose', surnames: 'Benítez'}, {names: 'José María', surnames: 'Núñez Rico'}, {names: 'Rodrigo', surnames: 'Núñez'}];
$scope.searchFn = function(actual, expected) {
if (angular.isObject(actual)) return false;
function removeAccents(value) {
return value.toString().replace(/á/g, 'a').replace(/é/g, 'e').replace(/í/g, 'i').replace(/ó/g, 'o').replace(/ú/g, 'u').replace(/ñ/g, 'n');
}
actual = removeAccents(angular.lowercase('' + actual));
expected = removeAccents(angular.lowercase('' + expected));
return actual.indexOf(expected) !== -1;
}
}
Check here for solutions to filter a list of objects and search keywords in every property of every object. It also maintain angular like search and also search with/ without accent characters.
Following is the filter definition:-
// ignore accents filter
$scope.ignoreAccents = function (item) {
if (!$scope.search) return true;
var objects = [];
var jsonstr = JSON.stringify(item);
var parsejson = JSON.parse(jsonstr);
var searchterm = $scope.search.replace(/[!#$%&'()*+,-./:;?#[\\\]_`{|}~]/g, ''); // skip replace if not required (it removes special characters)
objects = getNoOfResults(parsejson, searchterm);
return objects.length > 0;
};