I'm trying to use method from my $scope controller, imported from Factory and execute it on onlick method in Ionic based HTML.
I am still getting undefined and I don't know why, because any other variables from $scope are available (like userid). Is this scope hierarchy problem? I read through many articles, but I'm still stuck. Thanks.
Controller:
var mod = angular.module('Baybee.controller.nameList', []);
mod.controller('NameListCtrl', function ($scope, AllNamesList, UidGenerator, $localstorage) {
//importing list of names from Factory and unique user-id
$scope.namesFromService = AllNamesList.getList('AllNamesList');
$scope.userid = UidGenerator;
// test functions which I wasn't able to execute on 'onclick' method. scope.saveName should be that I wanted to use originaly
$scope.saveName = AllNamesList.transferName;
$scope.testb = function () {
console.log('working!');
};
});
Factory:
var mod = angular.module('Baybee.factory.allNamesList',['ionic'])
.factory('AllNamesList', function($localstorage) {
// 3 more list available as variables, here is just one
var AllNamesList = {
names: [
{
"name": "Jakub",
"set": [
"CzechCalendar",
"Top10Cz2015",
"Top50Cz2010"
],
"properties": {
"sex": "male",
"origin": "Hebrew",
"description": "Comes from Hebrew, יַעֲקֹב (Yaʿqob, Yaʿaqov, Yaʿăqōḇ)"
},
"popularity": [
{
"Cz": {
"2011": "10",
"2012": "7",
"2013": "6",
"2014": "6",
"2015": "7"
}
}
]
}
// more names here
]
};
return {
// returns right list with names based on string argument
getList: function(a) {
switch (a) {
case "AllNamesList":
return AllNamesList;
break;
case "loveNameList":
return loveNameList;
break;
case "maybeNameList":
return maybeNameList;
break;
case "noGoNameList":
return noGoNameList;
break;
default:
console.log("Sorry, can't find list with names");
}
},
// I would like to use this function on onlick method to transfer object with name properties to the right list (
transferName: function(name, listFrom, listTo){
// saving both list names into local memory
for (i = 0; i < listFrom.length; i++) {
if (name = listFrom[i]) {
listTo.push(listFrom[i]);
console.log(listFrom);
listFrom.splice(i, 1);
console.log(listTo);
}
else {
console.log('Cannot find: ' + name + ' in ' + listFrom);
}
}
}
}
});
HTML Body:
<body ng-app="baybee">
<ion-pane ng-controller="NameListCtrl">
<ion-header-bar class="bar-energized" >
<h1 class="title" >Baybee</h1 >
</ion-header-bar >
<ion-content >
<ion-list >
<div ng-repeat="names in namesFromService track by $index">
<ion-item ng-repeat="(key, value) in names"
on-swipe-right=""
on-swipe-left=""
//I would like to execute any function from scope here, but Im getting undefined
//onclick="testb()"
//onclick="saveName(name, 'list1', 'list2')"
<h2>{{value.name}}</h2>
<p>{{value.properties.origin}}</p>
</ion-item>
</div>
</ion-list >
</ion-content >
</ion-pane >
</body >
There is mistake in HTML part, I use onclick method, when it should be ng-click.
ng-click="saveName(name, 'list1', 'list2')"
Thanks
Related
This must be simple and Angular probably has an inbuilt directive to do this but I cant think of how to do without looping through the Array.
I have a array of options i.e.
$scope.colors=[
{id:"0",label:"blue"},
{id:"1",label:"red"},
{id:"2",label:"green"}
]
And then my data object that stores the id of a color option i.e.
$scope.data={
color:"1",
otherproperty:""
}
But when I display the data to the user I want to show the label rather than the id, so is there a easy(angular) way to do this?:
{{data.color.label}}
The Angular way would be using ng-repeat & filter, your still essentially looping over the Array but all options would require some sort of loop i.e.
<div ng-repeat="color in colors | filter:{ 'id': data.color}:true">
{{ color.label }}
</div>
Setting the Filter strict comparison to 'true' as above will only select the id with an exact match
https://jsfiddle.net/sjmcpherso/wztunyr5/
The following will return the object where the id matches $scope.data.color:
var pickedColor = $scope.colors.filter(function( obj ) {
return obj.id === $scope.data.color;
});
pickedColor.label will be the label string.
Look at other way, hope it will help you.
https://jsfiddle.net/kkdvvkxk/.
We can also use $filter under controller.
Controller :
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $filter) {
$scope.colors = [{
id: "0",
label: "blue"
}, {
id: "1",
label: "red"
}, {
id: "2",
label: "green"
}]
$scope.data = {
color: "1",
otherproperty: ""
}
$scope.getLabel = function(colorId) {
return $filter('filter')($scope.colors, { id: colorId }[0].label;
}
}
HTML :
{{ getLabel(data.color)}}
Following are my questions on the code described below -
On page load, my below custom sort filter is getting called multiple times. why?
On selecting a select option (from directive), I want to trigger filter based on selected value (using two way binding). Here again filter is being called thrice. Why?
Can some one point me to answers? I know that Angular does a Dirty Check by comparing old and new digest, but why multiple and 3rd invocation (as mentioned in the above questions).
I have below directive -
angular.module("customDirectives", [])
.directive("sortAll", function () {
return {
restrict: "E",
scope: {
columns: "=sortcolumns",
optionselected: "=selectedoption"
},
templateUrl: '../Views/Directives/SortAll.html',
controller: function ($scope) {
$scope.sortOptions = [];
var asc = 'Ascending';
var desc = 'Descending';
$scope.getSortOptions = function () {
angular.forEach($scope.columns, function (item) {
$scope.sortOptions.push({ name: item + '-' + asc, value: asc });
$scope.sortOptions.push({ name: item + '-' + desc, value: desc });
});
$scope.optionselected = $scope.sortOptions[1];
return $scope.sortOptions;
}
}
};
});
Directive HTML -
<select name="sortOptions" id="sortOptions" class="form-control width-20percent pull-right"
ng-options="option.name for option in sortOptions"
ng-init="getSortOptions()"
ng-model="optionselected"></select>
And below Filter -
angular.module("customFilters", [])
.filter("sort", function ($filter) {
return function (data, sortOption) {
console.log(sortOption);
if (angular.isArray(data) && angular.isObject(sortOption)) {
var options = sortOption["name"].split('-');
var xc = options[1] == 'Ascending' ? false : true;
return $filter("orderBy")(data, options[0], xc);
} else {
return [];
}
}
});
Now my Controller code -
angular.module("productStore")
.controller("ProductListCtrl", function ($scope, $filter) {
});
And the main controller which gives the data -
angular.module("productStore")
.constant("dataUrl", "http://localhost:57398/testdata/Products.json")
.constant("productColumns", ["name","price","description"])
.controller("MainCtrl", function ($scope, $http, dataUrl, productColumns) {
$scope.data = {};
$scope.productColumns = productColumns;
$http.get(dataUrl)
.success(function (products) {
$scope.data.products = products;
})
.error(function (error) {
$scope.data.error = error;
})
});
HTML -
<body ng-controller="MainCtrl">
<div class="navbar navbar-inverse">
<a class="navbar-brand" href="#">PRODUCT STORE</a>
</div>
<div class="panel-default" ng-controller="ProductListCtrl" ng-hide="data.error" ng-cloak>
<div class="col-xs-8">
<sort-All sortcolumns="productColumns" selectedoption="selectedSortOption"></sort-All>
<div class="well padding-top-0px" ng-repeat="product in data.products | sort:selectedSortOption">
<h3>
<strong>{{product.name}}</strong>
<span class="pull-right label label-primary">
{{product.price | currency}}
</span>
</h3>
<span class="lead">{{product.description}}</span>
</div>
</div>
</div>
</body>
Sample Data -
[{
"category": "Watersports",
"description": "A boat for one person",
"name": "Kayak",
"price": 275,
"id": "05af70919155f8fc"
}]
On Page load -
On changing item in select -
I have a Library angularJS application and a JSON Data file that contains an array of book's information like this
[
{
"name" : "test1",
"pages": 0,
"author": "author1",
"year": 1940,
"section": "history",
"current": 0,
"description": "bla bla bla",
"bookUrl": "data/book.pdf"
},
{
"name" : "test1",
"pages": 0,
"author": "author1",
"year": 1940,
"section": "history",
"current": 0,
"description": "bla bla bla",
"bookUrl": "data/book.pdf"
}
]
"current" is for the current page of the current book that i'm reading
and there is a "next" and "prev" buttons in the reading view
when i press "next" it adds "+1" to the "current" num page
the question is (How to send this +1 to the "current" in JSON file with PHP?)
I have this PHP code :
<?php
$jsonString = file_get_contents('library.json');
$data = json_decode($jsonString, true);
$data[0]['current'] = 3;
$newJsonString = json_encode($data);
file_put_contents('library.json', $newJsonString);
?>
See the ($data[0]) is for the index of the first book, how do i send to PHP the index of the current book so it updates the "current" data of the current book?
Here is the "next" function :
scope.goNext = function() {
if (scope.pageToDisplay >= pdfDoc.numPages) {
return;
}
scope.pageNum = parseInt(scope.pageNum) + 1;
$http.get("data/insert.php")
.success(function(data, status, headers, config) {
console.log("data inserted successfully");
});
};
And here is the reading Controller :
app.controller('read', ['$scope','books', '$routeParams',function($scope,books, $routeParams) {
books.success(function(data){
$scope.book = data[$routeParams.bookId]
$scope.pdfUrl = data[$routeParams.bookId].bookUrl;
$scope.pdfName = data[$routeParams.bookId].name;
});
//the pdf viewer options
//$scope.pdfUrl = 'data/tohfa12.pdf';
$scope.scroll = 0;
$scope.loading = 'Loading file please wait';
$scope.getNavStyle = function(scroll) {
if(scroll > 100) {
return 'pdf-controls fixed';
} else {
return 'pdf-controls';
}
};
$scope.onError = function(error) {
console.log(error);
};
$scope.onLoad = function() {
$scope.loading = '';
};
$scope.onProgress = function(progress) {
console.log(progress);
};
$scope.currentBookIndex = parseInt($routeParams.bookId);
}]);
I know it's complicated but i really need that , thanks.
How do you expect your application to behave?
You need to send the bookId and the pageNum with your request!
scope.goNext = function() {
if (scope.pageToDisplay >= pdfDoc.numPages) {
return;
}
scope.pageNum = parseInt(scope.pageNum) + 1;
$http.get("data/insert.php", {
param : {
bookId: $scope.bookId,
pageNum: $scope.pageNum
}
})
.success(function(data, status, headers, config) {
console.log("data inserted successfully");
});
};
BTW. By REST design a GET http request should never change a resource. GET is for READING. If you want to update a resource you should use POST, PUT or DELETE
I'm writting a questionnaires application, there are questions with responses, those responses can have child questions, these questions might have responses with another child questions then being a N level hierarchy, I need to find the best strategy to load this in a html list, using the normal ng-repeat I have a limit of level, in this example I chain 4 levels, but it could be more than that, I appreciate any comment or suggestion.
var myApp = angular.module('myApp',[]);
myApp.controller('myCtrl',function ($scope){
$scope.questionnaire = [
{
QuestionID: 1,
Description: "Question 1",
Responses: [{
RespDescription: "Response 1"
},
{
RespDescription: "Response 2",
ChildQuestions: [{
QuestionID: 2,
Description: "Child Question 2.1",
Responses: [{
RespDescription: "Child Response 2.1.1"
},
{
RespDescription: "Child Response 2.1.2",
ChildQuestions: [{
QuestionID: 3,
Description: "Child Question 2.1.2.1",
Responses:[{
RespDescription: "Child Response...",
ChildQuestions:[{
QuestionID:4,
Description: "Other Child Question",
Responses:[{
RespDescription: "Response..."
}]
}]
}]
}]
}]
}]
}]
}
];
})
I've done a similar questionnaire type app with a structure like that. What I did was to do is create a back-end api that has a tree-like structure of relations.
You want this to be hooked into a back end and not just written out, because otherwise it could get incredibly messy, a lot like a callback hell.
Here is the starting of the project on github. It uses loopback to do the data-modeling and hooks into an angular front-end, but you can use a back-end any way you like.
The idea is that when you query a first question, it has a few child answers. Each of those answers then has another question attached to it, and so on, and so on. The relationships of each model are whats important here.
This way you can create a controller that when you select a answerC to questionA, it would query the database for the related questionC object, and include all answers linked that that new questionC.
You would then add the newly loaded questionC with its answers to the main array of questions and scroll down (or something like that).
A quick sudo code example:
//controller.js
app.controller('questionair', function(Answer, Question){
//Lets load our first question, with the related 3 answers
Question.findById({id: 1}, {include: 'answers'}).$promise
.then(function(question){
$scope.questions = [question];
});
//function that gets new question from our select answer
$scope.answerMe = function(questionId){
Question.findById({id: questionId}, {include: 'answers'}).$promise
.then(function(newQuestion){
$scope.questions.push(newQuestion);
},function(error){
console.log('You\'ve answered the last question!');
});
};
});
//index.html
<div ng-repeat="question in questions">
<h2>{{ question.text }}</h2>
<ul>
<li ng-repeat="answer in question.answers"
ng-click="answerMe(answer.questionId)">
{{ answer.text }}
</li>
</ul>
</div>
I was through Mark Lagendijk's code in plunker and He had the solution for this task, recursivity is the secret,with a directive calling itself is possible to represent a N levels estructure, The key is the service called RecursionHelper that compile and avoid the infinite loop in the directive, I adapted the code to my necessity and this is the result:
RecursionHelper
/*
* An Angular service which helps with creating recursive directives.
* #author Mark Lagendijk
* #license MIT
*/
angular.module('RecursionHelper', []).factory('RecursionHelper', ['$compile', function($compile){
return {
/**
* Manually compiles the element, fixing the recursion loop.
* #param element
* #param [link] A post-link function, or an object with function(s) registered via pre and post properties.
* #returns An object containing the linking functions.
*/
compile: function(element, link){
// Normalize the link parameter
if(angular.isFunction(link)){
link = { post: link };
}
// Break the recursion loop by removing the contents
var contents = element.contents().remove();
var compiledContents;
return {
pre: (link && link.pre) ? link.pre : null,
/**
* Compiles and re-adds the contents
*/
post: function(scope, element){
// Compile the contents
if(!compiledContents){
compiledContents = $compile(contents);
}
// Re-add the compiled contents to the element
compiledContents(scope, function(clone){
element.append(clone);
});
// Call the post-linking function, if any
if(link && link.post){
link.post.apply(null, arguments);
}
}
};
}
};
}]);
questionTree Directive :
directives.directive('questionTree', function (RecursionHelper) {
return {
restrict: "AE",
scope: {
items: "=",
},
priority: 500,
replace: true,
//I use templateURL but for simplicity I used inline template in this code
template: function (el, attr) {
var itemType = attr["itemType"];
if (itemType == "question") {
return '<ul>'+
'<li ng-repeat="item in items">'+
'<div ng- click="loadChildResponses(item);$event.stopPropagation();">{{item.Description}}</div>'+
'<question-tree items="item.Responses" item-type="reponse"></question-tree>'+
'</li>'+
'</ul>';
}
else {
return '<ul>'+
'<li ng-repeat="item in items">'+
'<div ng-click="loadChildQuestions(item);$event.stopPropagation();">{{item.Description}}</div>'+
'<question-tree items="item.ModelWizardQuestions" item-type="question"></question-tree>'+
'</li>'+
'</ul>';
}
},
controller: function ($scope, $http) {
$scope.loadChildResponses = function (item) {
$http.get(siteUrls.GetReponses + "?QuestionID=" + item.QuestionID)
.success(function (data) {
if (data && data.length > 0) {
item.Responses = data;
}
});
};
$scope.loadChildQuestions = function (item) {
$http.get(siteUrls.getChildQuestions + "?ResponseID=" + item.ResponseID)
.success(function (data) {
if (data && data.length > 0) {
item.Questions = data;
}
});
};
},
compile: function (element) {
// Use the compile function from the RecursionHelper,
// And return the linking function(s) which it returns
return RecursionHelper.compile(element);
}
}
});
So, I load the first level of the questions, and attach the questionTree directive, and the application is able to load N levels.
The HTML:
<ul>
<li ng-repeat="question in Questions">{{question.Description}}
<ul>
<li ng-repeat="response in question.Responses"><span>{{response.Description}}</span>
<question-tree items="response.Questions" item-type="question"></question-tree>
</li>
</ul>
</li>
</ul>
I have problem with updating view by using ng-repeat.
When click on text, it doesnt update but it overwrites below. (I want have panel with names(links) and show its description on view)
I have searched everything and couldnt find answer or something useful what would help me.
html:
<body>
<div ng-app="myApp">
<div ng-controller="myCtrl">
<ul>
<li><a ng-repeat="item in items" ng-click="getInfo(item)" > {{item.name}} <br> </a></li>
</ul>
</div>
<hr>
<div ng-controller="myInfo">
<div ng-repeat="info in item" >
<h3>Name: {{info.name}}</h3>
<p> ID: {{info._id}}</p>
<p> temp: {{info.temp}} </p>
</div>
</div>
</div>
</body>
js
var app = angular.module('myApp', [])
app.controller('myCtrl', function($scope, $http, shareDataService) {
$http.jsonp('data.json').success(function(data) {
$scope.items = data;
});
$scope.getInfo = function(item) {
shareDataService.addItem(item);
}
});
app.controller('myInfo', function( $scope, shareDataService ){
$scope.item = shareDataService.getItem();
});
app.service('shareDataService', function() {
var myItem = [];
var addItem = function(newObj) {
myItem.push(newObj);
}
var getItem = function(){
return myItem;
}
return {
addItem: addItem,
getItem: getItem
};
});
json
angular.callbacks._0([
{
"_id": 1,
"temp": "asdgdf",
"name": "name1"
},
{
"_id": 2,
"temp": "asdasdasd",
"name": "name2"
},
{
"_id": 3,
"temp": "asasdasdadgdf",
"name": "name3"
}
]);
Plunker: http://plnkr.co/edit/X65oH0yAkRnN8npKnFY2?p=preview
You have an error in your console. Just add track by to your ng-repeat:
<div ng-repeat="info in item track by $index">
ng-repeat needs a unique id to track the items in order to be able to update them. If you add the same item twice, ng-repeat sees the same item twice, ans loses its mind. Using $index (which is unique) resolves that issue.
Keep in mind that using $index is adequate here, but it's preferred to use a unique id from the object if you can.
EDIT:
If your issue is that you want to see only the one element you clicked on in your view, then the issue is that you are adding your item to an array, when you should just be setting a value in your service. And, obviously, no need of a ng-repeat in your view.
http://plnkr.co/edit/Wfg9KhCWKMDreTFtirhR?p=preview
JS:
app.controller('myCtrl', function($scope, $http, shareDataService) {
//[...]
$scope.getInfo = function(item) {
shareDataService.setItem(item);
}
});
app.controller('myInfo', function( $scope, shareDataService ){
$scope.$watch(function () {
return shareDataService.getItem();
}, function (value) {
$scope.info = value;
})
});
app.service('shareDataService', function() {
var myItem;
return {
setItem: function(newObj) {
myItem = newObj;
},
getItem: function(){
return myItem;
}
};
});
HTML:
<div ng-controller="myInfo" ng-show="info">
<h3>Name: {{info.name}}</h3>
<p> ID: {{info._id}}</p>
<p> temp: {{info.temp}} </p>
</div>
If you only want to display information of the item that you just have clicked, then we don't need the second ng-repeat (as jlowcs said).
We also don't have to defined myItem as a array, it's just unnecessary, I think.
Edit:
how embarrassing i am, my answer look exactly to same as jlowcs's. I guess I took to much time to figure out the answer.
One thing to add up:
Why do I need a $watch in myInfo controller?
Because at the first time, we use ng-repeat, this component do the watch part for us. Then when I remove ng-repeat, I need to watch for data changing by myself.