I am building a cross platform app using Onsen UI, Monaca and AngularJS.
I have 2 screens. The first is where a user have to enter a Vehicle ID which calls an API. Based on the input Vehicle ID, I want to populate the API URL with this value and on the next screen display a list of all options that are returned as a JSON object based on the Vehicle ID.
E.g the API call looks as follows: mymadeupdomain/api/vehicle.php?id=109
Where 109 would be the ID the user enters to determine what will be displayed on the detailed vehicle screen.
I have hard-coded the values and I can read back the JSON object that is returned, but I cant seem to send the values for when the user enters them.
vehicle-id.html form that gets the vehicle id from the user
<form class="login-form" style="text-align: center" name="myForm">
<section style="padding: 8px">
<input type="text"
class="text-input--underbar"
required
minlength="3"
maxlength="10"
ng-model-options="{ debounce : 800 }"
placeholder="Vehicle ID / Fleet Number"
ng-model="fleetIDSearch" >
</section>
</form>
app.js is the controller for handling the form check
angular.module("myApp", ['onsen']).controller("vehicleController", function($scope, $http)
{
// Watch for changes on the vehicle ID screen
$scope.$watch('fleetIDSearch', function()
{
fetchFleetDetails();
});
$scope.fleetIDSearch = "";
function fetchFleetDetails()
{
$http.get("http://mymadeupdomain/api/vehicle.php?id=" + $scope.fleetIDSearch).success(function(data)
{
$scope.fleetIDs = data;
});
}
// Returns a list of all Vehivle Checks associated with Vehicle ID
$http.get("http://mymadeupdomain/api/getfleetchecks.php?fleetid=" + $scope.fleetIDSearch).success(function(data) // NOT WORKING HERE
{
$scope.checkItemDescriptions = data;
});
});
How do I get values entered in $scope.fleetIDSearch = ""; in the first screen and pass them to the API URL in the second screen?
I want to display a list of all checks associated with the ID as per below - working when API URL is hard coded
vehicleCheck.html
<ul class="list">
<li class="list__item" ng-repeat="checkItemDescription in checkItemDescriptions">
{{checkItemDescription.checkitemdesc}}
<label class="switch switch--list-item">
<input type="checkbox"
class="switch__input"
checked >
<div class="switch__toggle"></div>
</label>
</li>
</ul>
You have multiple choice in your case, at multiple levels.
If you want to simply share data between controllers, you can use Factories
Another solution is to:
Manage a ng-model on your first page
When the form is submitted, redirect to an url looking like /content/:id
Manage your request (fetching remote data) inside of the router resolver
Take the result directly in the other controller of the other page
Related
I want to set access permissions page where setting the permissions to the user to only view selected divs only for example:
There are 5 checkboxes in admin page and 5 divs in user page if check 3 div user has to get 3 div only I am mapping userlocation & client location & username for setting access permissions and how to display / hide the div in user page based on the checkbox selection or database values?
There is a user login separately and admin login separately. The admin will disable certain features for certain users. Using wcf rest and sql server as backend.
Before we get into the answer I have to mention security. You probably know, but for future readers benefit...you must not rely on simply hiding or not generating HTML elements as a means of restricting user access. Why - because when your form submits data to the server this can be observed with simple tools like Fiddlr. If you simply hide important inputs on the web page but still submit them, then a mischievous user could use something like Postman to edit and submit dodgy values to your server INCLUDING for the fields that were not visible on the form. Even if you have some server-side code to restrict the generation of the HTML for the restricted inputs, if a user is able to see a full form submit, or more likely if your API is self-describing or well documented, then they can fire up Postman and start sending your server all manner of hooky data.
For this reason it is vital that you re-validate the user, their role and their right to modify at the server at each interaction. Sermon over.
Assuming the above protection is in place then the way forward is relatively simple. In your HTML you assign classes to the elements that need to show/hide and you send a variable from the server to dictate their hidden or visible state.
For example, say you have two groups of users called usrNormal and usrAdmin. You might have an input defined as:
<input type="text" class="usrNormal usrAdmin" name="somedata" style="display:hidden;"></input>
<input type="text" class="usrAdmin" name="admindata" style="display:hidden;"></input>
<div class="usrNormal usrAdmin" style="display:hidden;">Some important info here....</div>
<div class="usrAdmin" style="display:hidden;">Some admin-only info here.... </div>
The key to this technique is the css class setting class="usrNormal usrAdmin"
And the accompanying JS function is:
var usrType = "usrNormal";
function protect() {
$("." + usrType).show();
}
protect();
I have used JQuery here in the line inside the function, you could use plain JS to achieve the same. We start the page with the important inputs, divs and other html elements hidden. The usrType setting comes from the server, and when the protect() function is called it finds all elements with the given user type class and makes them visible.
EDIT: See working snippet below. Though contrived the idea is clear I hope. One point to consider is the use of a generic marker class to indicate that the appropriate elements should take part in the show/hide operation. Let me know if you have any queries.
$( document ).ready(function() { // Important: wait for the document dom to be ready
// get the value of the server access variable - simulated by the radio buttons in this case
// var access = <you would put your server value here !>
var access = ".usrNormal";
setAccess(access);
// next part is just to let you play.
$('.serverVal').on('change', function(e){
setAccess($(this).val());
})
// key function - this will show pr hide based on classes.
function setAccess(accessVal) {
// next line finds all elements with class including 'usrAccess' and shows if they have the request class or otherwise hides.
$(".usrAccess").each( function() {
var ele = $(this); // readability
showHide(ele, accessVal);
})
}
// show or hide the element based on class
function showHide(ele, cls){
if ( ele.is(cls) ){ // pay attention - this uses the jquery 'is' feature.
ele.show();
}
else {
ele.hide();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form name="form1">
<p>
<span>This selection simulates the value passed from the server. When changed the elements with the matching classes are shown or hidden.</span><br />
</p>
<p>
<span>
<input type="radio" class="serverVal" id="usrType1" name="usrType" value=".usrNormal" checked="1" />
<label for="usrType1"> Normal User only</label>
</span>
<span>
<input type="radio" class="serverVal" id="usrType2" name="usrType" value=".usrAdmin"/>
<label for="usrType2"> Admin User only </label>
</span>
<span>
<input type="radio" class="serverVal" id="usrType3" name="usrType" value=".usrAdmin, .usrNormal" name="usrType3"/>
<label for="usrType3"> Both </label><br />
</span>
</p>
<hr>
<p class="usrNormal usrAccess" style="display:none;">
<label for="somedata">Normal only</label>
<input type="text" name="somedata" />
</p>
<p class="usrAdmin usrAccess" style="display:none;">
<label for="admindata1">Admin only</label>
<input type="text" class="usrAdmin" name="admindata1" />
</p>
<p class="usrNormal usrAccess" style="display:none;">
<label for="someinfo">Info 1</label>
<textarea id="someinfo">This textare is visible to normal users...</textarea>
</p>
<p class="usrAdmin usrAccess" style="display:none;">
<label for="admininfo">Info 2</label>
<textarea id="admininfo">This textare is visible to only Admin users...</textarea>
</p>
</form>
I have the following resource set up in my AngularJS app:
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
And by default they are listed like so via the controller:
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
function($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}]);
As you can see I already have a filter but I want a query string based search so that I can use the History to get back to the results after choosing a record, etc.
First question I have here, is that this list actually uses phones.json for the data but I don't specify this anywhere in the code... so how does this work? Presume some magic is happening but I can't see it.
So back to the initial question, I have built the following search controller:
phonecatControllers.controller('SearchCtrl', ['$scope', '$http', '$location', 'Phone',
function ($scope, $http, $location, Phone) {
$scope.keywords = $location.search()['q'];
// The function that will be executed on button click (ng-click="search()")
$scope.search = function() {
$location.path('phones').search('q', $scope.keywords);
$scope.phones= Phone.query($scope.keywords);
}
}]);
So it should use the query string to find the results. But how do I do this? It seems very transparent how the data is pulled from the JSON file. The method should also list data if the query string is there on page load... so perhaps this should be combined into one controller for both the list and search?
The code above doesn't filter the JSON data when I do a search... so the query string isn't being used... but I presume it's because I don't understand how the app knows to look in the 'phones.json' file?
The HTML for the filtering and search:
<div class="control-group">
<label class="control-label">Filter:</label>
<div class="controls">
<input ng-model="$parent.query">
</div>
</div>
<div class="control-group">
<label class="control-label">Sort by:</label>
<div class="controls">
<select ng-model="$parent.orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
</div>
<hr/>
<div ng-controller="SearchCtrl">
<form class="form-search" ng-submit="search()">
<label>Search:</label>
<input type="text" name="q" ng-model="keywords" class="input-medium search-query" placeholder="Keywords...">
<button type="submit" class="btn" ng-click="search()">Search</button>
</form>
</div>
The HTML for the list:
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp"
class="thumbnail phone-listing">
<img ng-src="{{phone.imageUrl}}">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
OK so to ensure that I understand correctly:
you already have a as-you-type search using Angular's filter filter
this search is implemented using an input bound to a variable named query
you are trying to persist the search terms when changing view and coming back
you want to persist it in the URL
You don't need the new controller or the new input. In the PhoneListCtrl controller, add $scope.query = $location.search().q. This will read the q parameter from the URL and write the value in query, which will automatically fill the input and filter your results.
To do the reverse (ie writing the value of query to the URL), add a ng-change attribute to your input (<input ng-model="query" ng-change="queryChanged()"/>), and add the corresponding function to your controller:
$scope.queryChanged = function () {
$location.search('q', $scope.query)
}
This function will be executed every time the query changes and it will update the URL accordingly. Everything should work out now. See this fiddle.
As a side note, persisting the query in the URL might not be the best idea, as it will remain visible is the user's browser after they have left the search view. You could use session storage, for example.
I'm starting with AngularJS, and I'm building a multi-step form where user has to fill different pages. When finished a page, he's allowed to press a next button and fill the following page.
For the first page, I've built in the HMTL a form (named pageOneForm), with different text input fields, marked as required, and in the relative controller I'm doing this watch:
$scope.$watch('pageOneForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
And it works like a charme. My model (ModelData) is updated.
I was trying to apply the same logic to the following part of the app, the second page. Instead of input text, the user has to select two options from 2 different radio buttons groups.
So I built in the html a list of buttons via ng-repeat :
<div ng-Controller="PageTwo" ng-show='data.actualPage == 2'>
<form name="pageTwoForm">
<h3>General Information > Knowledge About </h3>
<div>
<b>User</b>
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="data.knowledgeAboutUser" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
<div ng-repeat="option in targetGroupUserOptions">
<input type="radio" name = "targetUserGroup" ng-model="data.knowledgeAboutTargetGroup" ng-value="option.id" id="{{option.id}}" required>{{option.text}}
</div>
</div>
</form>
and I've implemented the same code as above in its controller:
$scope.$watch('pageTwoForm.$valid', function(validity) {
ModelData.actualPageCompleted = validity;
})
but apparently it doesn't work, and in my model actualPageCompleted is always true...
What am I doing wrong?
Thanks
I did my best to create a controller with some dummy data to get a fiddle working with your example code. Here is the fiddle You need to force the $digest cycle to update your form's validity state on ng-click for the radio buttons (see this SO post for more details), which is why the method
$scope.forceDigest = function(){
setTimeout(function(){ $rootScope.$$phase || $rootScope.$apply(); });
};
is necessary. Alternatively, you can get rid of the method call and uncomment the html code
<h3 ng-show="false">{{data.knowledgeAboutTargetGroup}}</h3>
<h3 ng-show="false">{{data.knowledgeAboutUser}}</h3>
in the fiddle to force the form object to update as well.
And I would make sure that ModelData.actualPageCompleted is not retaining its true value from when pageOneForm.$valid became true and it was set.
I hope that this helps!
I have a problem where I'm attempting to post the value of a checkbox in my model to the server and as the checkbox has not been interacted with on the form, angular seems to have not assigned it a value, when I ask for the value of the checkbox it comes back as undefined.
Here is my markup:
<div class="form-group">
<input id="templateDisable" type="checkbox" ng-model="template.disabled" />
<label for="templateDisable">Disabled</label>
</div>
And here's a reduced version of my save action on my controller:
$scope.save = function (form) {
if (form.$valid) {
var formData = new FormData();
// this is the problem line of code
formData.append("disabled", $scope.template.disabled);
// ... some other stuff
}
};
Actually, ticking then unticking the checkbox before I hit the save action results in the template.disabled property being false, which is what I would have expected without any manual intervention.
I've seen other related questions, e.g. AngularJS: Initial checkbox value not in model but surely stuff like a simple checkbox should be baked in? I shouldn't have to be writing directives to manage checkboxes surely?
This is per design. If you want a default value on your model than you should initialise it inside the controller (recommended), or make use of ng-init.
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.template = {
disabled = false
};
}
]
);
<div class="form-group">
<input type="checkbox" ng-model="template.disabled" ng-init="template.disabled=false" />
<label>Disabled</label>
</div>
The following will always set the state back to "unchecked" when the page is loaded (or refreshed). In other words it will overwrite the user's actual selection whenever the page is refreshed.
<input type="checkbox" ng-model="template.disabled"
ng-init="template.disabled=false" />
If, however, you want the checkbox state set to a default state initially and you also want it to remember user interactions, then the following is what you want.
<input type="checkbox" ng-model="template.disabled"
ng-init="template.disabled = template.disabled || false" />
Problem
I have a form which is divided into five different sections. Find the Image below. When a person comes on this page I direct him/her directly to the Summary stage with pre-populated data which is working fine. Where the problem comes is there's an option for the user to go n edit this form by clicking on prev button. After the user changes the value in the previous stages and comes back to the summary stage it still shows them the default information which came through the pre-populated data but when a user submit form it submits the correct info with new edited data.
Is there any way I can refresh or reload it? I'm using the below code to display this summary.
Code
//Button to reach the summary stage
<button type="button" class="next" id="summary" onclick="loadnext(4,5);"><img src="images/next.jpg" alt="" /> </button>
//Display
<tr>
<td>Team Name</td>
<td id='t_name_SUM'></td>
</tr>
<tr>
<td>Team Visibility</td>
<td id='t_visibility_SUM'></td>
</tr>
To better support my comment I'm writing this answer.
What you need to do is provide a mechanism to collect data step by step. Copying HTML into other elements is not a very flexible way to do that.
I created a jsfiddle to demonstrate the idea. It's far from perfect but at this stage it works without making things to complicated.
The basic idea is to have an object to control the flow of the views(steps) the user needs to handle. Each view has a controller associated with it to handle the data.
The views.
<div id="step1" class="view">
<h3>Step #1</h3>
<input type="text" name="name" value="" />
</div>
<div id="step2" class="view">
<h3>Step #2</h3>
<input type="text" name="title" value="" />
</div>
<div id="step3" class="view">
<h3>Step #3</h3>
<input type="text" name="message" value="" />
</div>
The object controlling the flow (only showing the most relevant parts).
var flow = {
...
settings:{}, // settings to save
routes: [
{controller:Step1Ctrl, element:'#step1'},
{controller:Step2Ctrl, element:'#step2'},
{controller:Step3Ctrl, element:'#step3'}
],
...
show: function (index) {
var route = this.routes[index];
if (this.currentController) {
this.currentController.commit();
}
this.index = index;
this.currentController = new route.controller(
this.settings,
route.element
);
}
};
Every time a new view is shown the current controller commits the changes to settings and a new controller is created for the current view.
A simple controller can look like this.
function Step1Ctrl(settings, element) {
var $e = $(element).find('input[name="name"]');
$e.val(settings.name); //set the initial value
this.commit = function () {
settings.name = $e.val(); //save
};
}
Since every controller now commits it's changes to settings it easy to represent that data in any way you like. The fiddle just alerted the collected data but you can easily create another view and another controller to display thse settings.
Also, because every controller has access to the settings it can act upon values that are already in there or check if a combination of settings is even possible or not.