Here is my problem:
I have a button, with I can add dynamically inputs.And specifically add a textarea with div element above it which contains a counter for chars in textarea.The problems is that angular tags are rendering as ordinary html text.
mainController :
var validationApp = angular.module('validationApp', []).config(function ($interpolateProvider) {
$interpolateProvider.startSymbol('<%').endSymbol('%>');
});
validationApp.controller('mainController', function ($scope, $compile) {
var max_fields = 10; //maximum input boxes allowed
var wrapper = $(".activities_wrap"); //Fields wrapper
var add_button = $(".my-add_field_button"); //Add button ID
var x = 0; //initlal text box count
var elements = "<div class=\"char-counter\">Остават Ви <% (100 - projectManagement['project_management[0][activity]'].$viewValue.length) > 0 ? (100 - projectManagement['project_management[0][activity]'].$viewValue.length) : 0 %> символа" + +"</div>" +
"<div><h3>Дейност #" + x + "</h3>" +
"<textarea ng-model=\"project_management.activity" + x + "\" ng-maxlength=\"100\" name=\"project_management[" + x + "][activity]\" placeholder=\"Дейност\"></textarea>" +
"<input class=\"from\" type=\"text\" name=\"project_management[" + x + "][from]\" placeholder=\"Начална дата\">" +
"<input class=\"to\" type=\"text\" name=\"project_management[" + x + "][to]\" placeholder=\"Крайна дата\">" +
"<input type=\"text\" name=\"project_management[" + x + "][place]\" placeholder=\"Място\">" +
"<input type=\"text\" name=\"project_management[" + x + "][responsible_for_activity]\" placeholder=\"Отговорен за дейността\">" +
"<input type=\"text\" name=\"project_management[" + x + "][count_participants]\" placeholder=\"Брой включени участници\">" +
"<textarea type=\"text\" name=\"project_management[" + x + "][indicators_for_measure_of_activity]\" placeholder=\"Индикатори за измерване на дейността\"></textarea>" +
"<br>Премахни</div>";
$(add_button).click(function (e) { //on add input button click
e.preventDefault();
if (x < max_fields) { //max input box allowed
x++; //text box increment
$(wrapper).append(elements); //add input box
$('.from, .to').datepicker({
dateFormat: 'dd-mm-yy',
dayNames: ["Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
dayNamesMin: ["Нд", "По", "Вт", "Ср", "Чт", "Пт", "Сб"],
monthNames: ["Януари", "Февруари", "Март", "Април",
"Май", "Юни", "Юли", "Август", "Септември",
"Октомври", "Ноември", "Декември"]
}).val();
$compile(elements)($scope);
}
});
$(wrapper).on("click", ".remove_field", function (e) { //user click on remove text
e.preventDefault();
$(this).parent('div').remove();
x--;
})
});
HTML :
<fieldset ng-form="projectManagement">
<div class="activities_wrap">
<button class="action-button my-add_field_button">Добави дейност</button>
</div>
<input type="button" name="previous" class="previous action-button" value="Назад"/>
<input type="button" name="next" class="next action-button" ng-disabled="projectManagement.$invalid" value="Напред"/>
<script>
$('.from, .to').datepicker({
dateFormat: 'dd-mm-yy',
dayNames: ["Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
dayNamesMin: ["Нд", "По", "Вт", "Ср", "Чт", "Пт", "Сб"],
monthNames: ["Януари", "Февруари", "Март", "Април",
"Май", "Юни", "Юли", "Август", "Септември",
"Октомври", "Ноември", "Декември"]
}).val();
</script>
This is not exactly an answer, but a simple example that will, hopefully, help you understand the "angular thinking".
AngularJS is different that JQuery in a way that your view is completely dependent of your data structure. Changes in your data will reflect in view changes, in the same way that, changes in the view may reflect in an update to your data. This is all done by a mechanism called Data-Binding. I'm not that good at explaining so a made a simple example. Besides, you should really study about Data-Binding, ViewModel pattern and Angular in general. You will see that, thankfully, you will not need Jquery for much.
So, to achieve what you want, you need to hold an array of objects in your controller, and push new items to it with the click of a button. This array will store objects with the properties you want to store, like ID, NAME, etc. Each of those properties will be binded to a input, so that they can be edited.
Here is an example of what I'm trying to explain.
https://jsfiddle.net/relferreira/42zrn2t2/1/
JS
angular.module('app', []);
angular.module('app')
.controller('MainController', mainController);
mainController.$inject = ['$scope'];
function mainController($scope){
var vm = this;
vm.itemsList = [];
vm.addItem = addItem;
function addItem(){
vm.itemsList.push({});
}
}
HTML
<div data-ng-app="app">
<div data-ng-controller="MainController as mainVm">
<button data-ng-click="mainVm.addItem()">ADD</button>
<table>
<thead>
<th>ID</th>
<th>Name</th>
</thead>
<tbody>
<tr data-ng-repeat="item in mainVm.itemsList">
<td><input type="number" data-ng-model="item.id"></td>
<td><input type="text" data-ng-model="item.name"></td>
</tr>
</tbody>
</table>
{{mainVm.itemsList}}
</div>
</div>
Take a look at this question for more information
"Thinking in AngularJS" if I have a jQuery background?
Related
I have to use AngularJS to build a dashboard and one of the components is a table.
Since I did not find relevant dependencies/libraries for angularjs (like tabulator or datatables), I am doing it myself.
Instead of using the native angular filter, I built a custom method, but I am not sure if I am following a good approach.
The main idea is that when I pull the data object (array of objects) via Ajax, I create both an "original" and a "current" data object,s and at the beginning, they are exactly the same of course.
Then I created an input field above every column heading and I linked the search function to the blur and keyup events (enter key).
When the search function is triggered, I start making changes to the "current" object. This way I can filter by multiple columns incrementally. I filter the data object using an awesome library called AlaSQL.
I also linked to a button the "reset" method, which simply makes the "current" object equal to the "original" object, and cleans up the input fields.
The point is, am I missing any best practices? Are there better ways to do so with AngularJS?
Any suggestions?
Thanks a lot.
HTML
<div ng-app="myApp">
<div ng-controller="divController">
<my-table></my-table>
</div>
</div>
JS
var app = angular.module('myApp', []);
app.controller('divController', function ($scope, $http) {
$scope.data = {};
$scope.data.current = null;
$scope.data.original = null;
$scope.filter = {
id: {
field: "id",
value: null
},
name: {
field: "name",
value: null
},
owner: {
field: "owner",
value: null
},
}
$scope.reset = function () {
console.log("reset");
$scope.data.current = $scope.data.original;
for (let prop in $scope.filter) {
$scope.filter[prop]["value"] = null;
}
}
$scope.filterExec = function (field, value) {
if (value) {
console.log(`Executing filter on field "${field.trim()}" by this value "${value.trim()}"`);
var filtered = alasql('SELECT * FROM ? where ' + field + ' LIKE "%' + value + '%"', [$scope.data.current]);
$scope.data.current = filtered;
}
}
$http.get("./workspaces_demo_obj.json")
.then(function (response) {
console.log(response);
$scope.data.original = response.data;
$scope.data.current = response.data;
});
});
app.directive('myTable', function () {
return {
template:
'<div>Total rows {{data.current.length}} <button ng-click="reset()">RESET</button></div>' +
'<table class="table table-responsive table-sm">' +
'<thead>' +
'<tr><th>Workspace ID</th>' +
'<th>Name</th>' +
'<th>Owner</th></tr>' +
'<tr><th><input ng-model="filter.id.value" ng-blur="filterExec(filter.id.field, filter.id.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.id.field, filter.id.value)" placeholder="Filter by id"></input></th>' +
'<th><input ng-model="filter.name.value" ng-blur="filterExec(filter.name.field, filter.name.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.name.field, filter.name.value)" placeholder="Filter by name"></input></th>' +
'<th><input ng-model="filter.owner.value" ng-blur="filterExec(filter.owner.field, filter.owner.value)" ng-keydown="$event.keyCode === 13 && filterExec(filter.owner.field, filter.owner.value)" placeholder="Filter by owner"></input></th></tr>' +
'</thead>' +
'<tbody>' +
'<tr ng-repeat="x in data.current">' +
'<td>{{ x.workspace_id }}</td>' +
'<td>{{ x.name }}</td>' +
'<td>{{ x.owner }}</td>' +
'</tr>' +
'</tbody>' +
' </table>',
restrict: 'E'
};
});
I have the following
index.cshtml
<td>From:<input kendo-date-picker ng-model="date1" class="form-control input-sm" /></td>
<td>To:<input kendo-date-picker ng-model="date2" class="form-control input-sm" /></td>
<tr dir-paginate="item in AllItems| itemsPerPage: 15 | filter : dateRangeFilter:date1:date2"> #*filter : SearchSubmitDate | filter : MarketingSearch filter: dateRangeFilter('SubmitDate', startDate, endDate)*#
<td style="font-weight:bold;">{{item.MarketingFormattedID}}</td>
<td filter="{ 'SubmitDate': 'text' }">{{item.SubmitDate| mydate | date: 'MM/dd/yyyy'}}</td>
<td>{{item.DueDate| mydate | date: 'MM/dd/yyyy'}}</td>
<td>{{item.Description}}</td>
<td>{{item.SubmittedBy}}</td>
<td>{{item.MarketingStatusTypeName}}</td>
<td>{{item.AssignedTo}}</td>
<td><button ng-click="GetByMktId(item.MarketingID)"> Edit</button></td>
</tr>
in .js file
function parseDate(input) {
return Date.parse(input);
}
app.filter("dateRangeFilter", function () {
return function (items, from, to) {
console.log('dateRangeFilter ' + from + ' ' + to);
var df = parseDate(from);
var dt = parseDate(to);
console.log('parsed ' + df + ' ' + dt);
var result = [];
for (var i = 0; i < items.length; i++) {
var date_bet = items[i].datetime;
if (date_bet > df && dt > date_bet) {
result.push(items[i]);
}
}
return result;
};
});
Everything comes as undefined. I am relatively new to angularjs and tried all the different ways i could find on stackoverflow or google. not sure what i am missing. please help. thanks.
Additional Info:
It is a very big controller so i didn't include all the info.
app.controller('cvManageMarketingController', function ($scope, serviceMarketing, updateMarketingItemService, marketingItemsListService,
serviceNewMarketCode, serviceSaveMktFiles, serviceGetMktFiles, serviceDownloadMktFile, $filter){
marketingItemsListService.getAll().then(function (value) {
$scope.AllItems = (value);
$scope.orderByField = 'SubmitDate';
$scope.reverseSort = true;
$scope.AllFiles = [];
$scope.MarketingID = 0;
$scope.MarketingFormattedID = "";
$scope.Headers = "Add Marketing Item";
});
}
and i pass ng-controller="cvManageMarketingController" to the main div.
I would like to suggest you to use smart table plugin for populating the items in angular js which comes with handy api's and all needed things like pagination , items to display etc..
Im trying to output the following info and have it update realtime using angulars automatic binding. the first and last update ok, but full isnt updating as I would expect. im grateful for any assistance.
http://jsfiddle.net/laurencefass/74u313gx/1/
required output:
first = john
last = doe
full name=john doe.
HTML
<div ng-app="nameApp">
<div ng-controller="nameController">
<input ng-model="first"/>
<p>first = {{first}}</p>
<input ng-model="last"/>
<p>last = {{last}}</p>
<p>full name = {{full}}</p>
</div>
JS
var app=angular.module("nameApp", []);
app.controller('nameController', function($scope) {
$scope.first="john";
$scope.last="doe";
$scope.full = $scope.first + " " + $scope.last;
});
initial output seems correct and the first and last update as expected. but the full name is not updating despite being a $scope var and a product of first and last.
The following line is only run once. So it is initiated with the first value that has been assigned to first and last.
$scope.full = $scope.first + " " + $scope.last;
So, if you want your binding to work, without having a unnecessary fonction into your controller. (keep your controllers as clean as possible!)
<div ng-app="nameApp">
<div ng-controller="nameController">
<input ng-model="first"/>
<p>first = {{first}}</p>
<input ng-model="last"/>
<p>last = {{last}}</p>
<p>full name = {{first + ' ' + last}}</p>
</div>
</div>
Have a look at your fiddle :
http://jsfiddle.net/74u313gx/2/
If you really need to have the fullname into the controller, you can use a $watch therefor :
$scope.$watch('first', updateFull);
$scope.$watch('last', updateFull);
function updateFull(){
$scope.full = $scope.first + " " + $scope.last;
}
If you are concerned by performance, you may want to avoid defining to much watches, then a ng-change can be used :
controller :
$scope.updateFull = function(){
$scope.full = $scope.first + " " + $scope.last;
}
View :
<div ng-app="nameApp">
<div ng-controller="nameController">
<input ng-model="first" ng-change="updateFull();"/>
<p>first = {{first}}</p>
<input ng-model="last" ng-change="updateFull();"/>
<p>last = {{last}}</p>
<p>full name = {{full}}</p>
</div>
Since you're defining a string on $scope that is concatenated with several parameters, it won't change automatically when you change the parts that you used to assemble it with.
If you want to achieve what you're looking for, you can do one of two things:
One:
<p>full name = {{first + ' ' + last}}</p>
Two:
<p>full name = {{getFullName()}}</p>
And in the controller have a function:
$scope.getFullName = function () {
return $scope.first + ' ' + $scope.last;
}
it will not update auto since your are passing a string in full name after concatenation it returns simple string not an angular var, you need to do the following to update value automatically.
$scope.$watchGroup(["first","last"],function(){
$scope.full = $scope.first + " " + $scope.last;
});
update your code like below:
View Update
<div ng-app="nameApp">
<div ng-controller="nameController">
<input ng-model="first" ng-change="change()"/>
<p>first = {{first}}</p>
<input ng-model="last" ng-change="change()" />
<p>last = {{last}}</p>
<p>full name = {{full}}</p>
Script Update
var app=angular.module("nameApp", []);
app.controller('nameController', function($scope) {
$scope.first="john";
$scope.last="doe";
$scope.full = $scope.first + " " + $scope.last;
$scope.change= function(){
$scope.full=$scope.first + " " + $scope.last;
}
});
As you seen above, I am changed/update view page input elements with ng-change directive and create same scope function to namecontroller w.r.t.nameApp Module.
Hope its helps you!!!
I'm trying to set an initial background color on a set of div's that are created using ng-repeat. I also want to update the background color for each div on hover.
I'm able to see the correct color on hover, but I'm not sure how I can set the initial background color for each div when I have a variable in the ng-style. I did try looping through the projects in the controller and calling the rgba function in my controller, but it just applies that last background color to all of the divs.
Here is my ng-repeat block:
<section ng-controller="ProjectsCtrl" class="work">
<div ng-repeat="project in projects" class="work-example" ng-style="{'background-color': '{{ project.background_color }}'}">
<a href="#">
<div class="inner" ng-style="{'background-image': 'url({{ project.image_url }})'}">
<div class="type">{{ project.title }}</div>
<div class="client">{{ project.client }}</div>
<div class="overlay" ng-style="background" ng-mouseover="rgba(project.background_color, 0.2)"></div>
</div>
</a>
</div>
</section>
My controller has a function called rgba that will take the hex (coming from rails api call) and turn it into rgba.
App.controller('ProjectsCtrl', ['$scope', 'Projects', function($scope, Projects) {
$scope.rgba = function(hex, opacity) {
var hex = hex.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
$scope.background = { 'background-color': result }
}
$scope.projects = Projects.query();
}
]);
Here is the service my controller is calling:
App.factory('Projects', ['$resource', function($resource) {
return $resource('/api/projects/:id', {
id: '#id'
});
}
]);
Here is my attempt to update ng-style from the controller (but assigns all divs the last background color):
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
});
I'm pretty new to the AngularJS world, so I hope all of this makes sense. Any help is greatly appreciated. Thanks!
The reason why "it applies that last background color to all of the divs" is because, of the following code.
$scope.rgba = function(hex, opacity) {
var hex = hex.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
$scope.background = { 'background-color': result }
}
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
});
When your angular.forEach runs, it is invoking $scope.rgba which is in turn updating the value of $scope.background to the latest background color. Inside your HTML markup, you have <div class="overlay" ng-style="background" ng-mouseover="rgba(project.background_color, 0.2)"></div> which looks for a variable called background in $scope.
Now the catch here is, as this markup is inside an ng-repeat every single repetition of the markup will have the same value for ng-style as everything is looking at the same object $scope.background.
Instead, what I would suggest you to do is the following.
Projects.query(function (projects) {
$scope.projects = projects; // <- $scope.projects is set when the query completes
angular.forEach(projects, function (value, index) {
$scope.rgba(project, '0.8');
});
});
$scope.rgba = function(project, opacity) {
var hex = project.background_color.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
project.backgroundStyle = { 'background-color': result }
}
And then your markup:
<section ng-controller="ProjectsCtrl" class="work">
<div ng-repeat="project in projects" class="work-example" ng-style="{'background-color': '{{ project.background_color }}'}">
<a href="#">
<div class="inner" ng-style="{'background-image': 'url({{ project.image_url }})'}">
<div class="type">{{ project.title }}</div>
<div class="client">{{ project.client }}</div>
<div class="overlay" ng-style="project.backgroundStyle" ng-mouseover="rgba(project.background_color, 0.2)"></div>
</div>
</a>
</div>
</section>
I believe this would solve your issue of every div having the latest background.
$resource.query returns a wrapper object containing a promise instead of the actual results of the query.
So with the following code:
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
})
You are actually setting $scope.projects to the promise wrapper.
You need to change it to something like this:
Projects.query(function (projects) {
$scope.projects = projects; // <- $scope.projects is set when the query completes
angular.forEach(projects, function (value, index) {
$scope.rgba(value.background_color, '0.8');
});
});
I am facing a problem with my angular js bindings not being updated correctly.
I am trying to achieve a way to hide certain form elements and show others by clicking a "next" button.
I have setup some objects in my controller to hold values for input text fields and menu dropdowns, I also have setup a couple of button (next and previous and add) button to be able to add new objects and a next and previous buttons to be able to navigate between the different stored objects.
The problem that I am facing is that the input text field is being updated correctly when i press the next and previous button however the dropdown menus are not.
This is a link to a jsfiddle to help show the problem:
http://jsfiddle.net/bLs9yu3f/
Found two issues with the code in your Fiddle:
First, when assigning programOutcomes to the affects key of your objects (both when creating the initial one and pushing to add a new one) you where assigning programOutcomes directly, which assigns a pointer to the original array and doesn't create a copy. There are many ways to do this. I chose affects: JSON.parse(JSON.stringify(programOutcomes)). See the example below.
$scope.output.outcomes.push({
outcome: '',
affects: JSON.parse(JSON.stringify(programOutcomes))
});
Second, in the for loop of your addCourseOutcome function you refer to $scope.output.outcomes[0] instead of the latest $scope.output.outcomes you just pushed. The following code fixes this issue.
var lastest = $scope.output.outcomes.length - 1;
for (var i = 0; i < programOutcomes.length; i++) {
$scope.output.outcomes[lastest].affects[i].how = '';
}
This is a fork of your Fiddle with the corrections I mentioned above: http://jsfiddle.net/JohnnyEstilles/uz8zf2b0/.
angular.module('myapp', []).controller('ProgramsController', ['$scope',
function($scope) {
var programOutcomes = [{
outcome: 'po1'
}, {
outcome: 'po2'
}, {
outcome: 'po3'
}, {
outcome: 'po4'
}];
$scope.input = {
outcomeCounter: 0,
programOutcomes: programOutcomes,
actions: ['', 'I', 'E', 'R']
};
$scope.output = {
outcomes: [{
outcome: '',
affects: JSON.parse(JSON.stringify(programOutcomes))
}]
};
for (var i = 0; i < programOutcomes.length; i++) {
$scope.output.outcomes[0].affects[i].how = '';
}
$scope.nextOutcome = function() {
$scope.input.outcomeCounter++;
};
$scope.previousOutcome = function() {
$scope.input.outcomeCounter--;
};
$scope.deleteCourseOutcome = function() {
$scope.output.outcomes.splice($scope.input.outcomeCounter, 1);
$scope.input.outcomeCounter--;
};
$scope.addCourseOutcome = function() {
$scope.output.outcomes.push({
outcome: '',
affects: JSON.parse(JSON.stringify(programOutcomes))
});
/**
* create a 'how' property in the affects array
* to be used for storage of I, E, R
*/
var lastest = $scope.output.outcomes.length - 1;
console.log($scope.output.outcomes[lastest].affects);
for (var i = 0; i < programOutcomes.length; i++) {
$scope.output.outcomes[lastest].affects[i].how = '';
}
/**
* increment the outcomeCounter
*/
$scope.input.outcomeCounter++;
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myapp">
<div ng-controller="ProgramsController">
<div class="form-group">
<label for="outcome">Outcome</label>
<input id="outcome" placeholder="Outcome" class="form-control" ng-model="output.outcomes[input.outcomeCounter].outcome">
</div>
<div class="form-group">
<table class="table table-striped">
<tr ng-repeat="programOutcome in input.programOutcomes">
<td>{{programOutcome.outcome}}</td>
<td>
<select ng-model="output.outcomes[input.outcomeCounter].affects[$index].how" ng-options="value for value in input.actions">
</select>
</td>
</tr>
</table>
</div>
<div class="form-group">
<button class="btn" ng-click="addCourseOutcome()">Add outcome</button>
<button class="btn" ng-click="nextOutcome()"
ng-if="output.outcomes.length>1 && input.outcomeCounter !== (output.outcomes.length - 1)">
Next
</button>
<button class="btn" ng-click="previousOutcome()"
ng-if="output.outcomes.length>1 && input.outcomeCounter > 0">
Previous
</button>
<button class="btn btn-warning" ng-click="deleteCourseOutcome()">Delete outcome</button>
</div>
</div>
</body>