I'm using AngularJS + ui-router to make some wizard with nested forms and routes.
here is the main form:
<div id="form-container-wizard">
<div class="form-horizontal" role="form">
<form name="addItem_form" ng-submit="submitForm()">
<div class="page-header text-center">
<h2>Post Your Item</h2>
<!-- the links to our nested states using relative paths -->
<!-- add the active class if the state matches our ui-sref -->
<div id="status-buttons-wizard" class="text-center">
<a ng-class="{ disabled: ItemCheckPass }" ui-sref-active="active" ui-sref=".item"> <span></span>Item</a>
<a ng-class="{ disabled: !ItemCheckPass || LocationCheckPass}" ui-sref-active="active" ui-sref=".location"><span></span>Location</a>
<a ng-class="{ disabled: !ItemCheckPass || !LocationCheckPass || AccountCheckPass}"ng-show="!IsAuthenticated" ui-sref-active="active" ui-sref=".account"><span></span>Account</a>
<a ng-class="{ disabled: !ItemCheckPass || !LocationCheckPass || !IsAuthenticated && !AccountCheckPass }"ui-sref-active="active" ui-sref=".social"><span></span>Social</a>
</div>
</div>
<div class="panel panel-default">
<div id="form-views" ui-view></div>
</div>
</form>
</div>
</div> <!-- wizard container -->
here is the routing for the form:
.state('post_add', {
url: '/post_add',
templateUrl: '/view/post_wizard/form.html',
controller: 'postWizardMainController',
abstract:true
})
.state('post_add.item', {
url: '',
templateUrl: '/view/post_wizard/form-item.html',
controller: 'postWizardController'
})
.state('post_add.location', {
url: '/location',
templateUrl: '/view/post_wizard/form-location.html',
controller: 'postWizardController'
})
.state('post_add.account', {
url: '/account',
templateUrl: '/view/post_wizard/form-account.html',
controller: 'postWizardController'
})
.state('post_add.social', {
url: '/social',
templateUrl: '/view/post_wizard/form-social.html',
controller: 'postWizardController'
});
each view contains a partial form and store the form elements values to $scope.AddItem object by using ng-models like <input type="email" class="form-control" name="email" ng-model="AddItem.email" ng-minlength=3 ng-maxlength=30 required>
postWizardMainController used to keep the validation variables and methods.
So, the problem is:
option 1: the code is as listed here, <form name="addItem_form" ng-submit="submitForm()"> is not getting submitted by <input type="submit" ng-disabled="addItem_form.$invalid" class="btn btn-orange" value="Post An Ad"> located in the last of form views.
option 2: i put the submitForm() into the ng-click in the last form, and locate submitForm() function in the postWizardMainController. In this option, the submitForm() function is called, bit no objects are passed into it. $scope.AddItem is undefined.
So, the question is:
How can i submit the form and pass $scope.AddItem object into submission, which must contain the data from all the nested forms.
The solution was to declare $scope.AddItem = {} in postWizardMainController
Related
There is a following code snippet:
<my-header></my-header>
<div ng-switch="$ctrl.page">
<div ng-switch-when="1"><component1></component1></div>
<div ng-switch-when="2"><component2></component2></div>
<div ng-switch-when="3"><component3></component3></div>
</div>
I want that component myHeader would be constructed before ngSwitch directive takes an action. Now component1 is constructed before myHeader.
Routing represents following code:
$stateProvider
.state({
name: 'myApp',
url: '/',
component: 'loader',
})
.state({
name: 'myApp.pages',
url: 'pages/{id:int}',
component: 'loader'
});
$urlRouterProvider.otherwise('/pages/1');
You can achieve this by exposing your controller in the link function inside the myHeader directive.
With that, you can easily add variables to the controller and control the visibility of the ng-switch div with ng-if. Check the code snippet down here.
Ah, don't forget to add ng-cloak to the div containing the ng-switch directive.
angular
.module('app', [])
.controller('TestController', function($scope) {
this.page = 1;
})
.directive('myHeader', function () {
return {
link: function (scope, element, attrs) {
// With element.controller() we can reach the controller that is wrapping our directive. Then, we can simply set the headerIsLoaded variable to true.
element.controller().headerIsLoaded = true;
},
scope: true,
templateUrl: 'my-header.html'
}
});
<div ng-controller="TestController as ctrl">
<my-header></my-header>
<!-- Add a visual feedback so user knows the components are being loaded -->
<div ng-if="!ctrl.headerIsLoaded">
Loading...
</div>
<!-- If ctrl.headerIsLoaded is set to true, the ng-switch will appear -->
<div ng-if="ctrl.headerIsLoaded"
ng-cloak>
<div ng-switch="ctrl.page">
<div ng-switch-when="1">
Page 1
</div>
<div ng-switch-when="2">
Page 2
</div>
<div ng-switch-when="3">
Page 3
</div>
</div>
</div>
</div>
I am trying to use directive in mu page ..I am trying to send data from controller to directive .I am trying to display data using ng-repeat.
here is I am applying the ng-repeat .
<div class="container">
<!--Row with two equal columns-->
<div class="row " ng-repeat='d in data'>
<div class="col-sm-3">
<div class="demo-content">{{d.Location}}</div>
</div>
<div class="col-md-7">
<div class="demo-content bg-alt description-ellipse">{{d.description}}</div>
</div>
<div class="col-md-1">
<div class="demo-content bg-alt">
<button type="button" class="btn btn-default view-now-button">VIEW NOW</button>
</div>
</div>
</div>
</div>
I make a directive like this
.directive('listComponent', function() {
return {
restrict: 'E',
scope: {
data:'='
},
templateUrl: 'list.html',
link: function(s, e, a) {
}
}
})
I am sending data like that
data:'=' and from here <list-component data='h.data'></list-component>
here is my code
http://plnkr.co/edit/Q0GQC2Pik7m25HxQIW8H?p=preview
It should be:
<div class="row " ng-repeat='d in data.jobs'>
in list.html
Or, as per comment:
<list-component data='h.data.jobs'></list-component>
Updated Plunker
I am trying to learn Angular JS with an HTML Sample. I would like the user to fill out some basic information, and based on the checkbox they select, it will load a form page using the UI Routing. It will generate links to navigate the page automatically, based on the checkboxes selected. Then, once the form is complete it should save in a directory on the server, and download to the user computer.
I got the form to show all data as json array, but now nothing is working after trying to add the ability to create the checklist links, as navigation, and saving?
App.js
create our angular app and inject ngAnimate and ui-router
angular.module('formApp', ['ngAnimate', 'ui.router'])
//configuring our routes
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'form.html',
controller: 'formController'
})
// nested states
// each of these sections will have their own view
// url will be nested (/form/profile)
.state('form.profile', {
url: '/profile',
templateUrl: 'form-profile.html'
})
// url will be /form/interests
.state('form.interests', {
url: '/interests',
templateUrl: 'form-interests.html'
})
// url will be /form/payment
.state('form.payment', {
url: '/payment',
templateUrl: 'form-payment.html'
});
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/profile'); })
// our controller for the form //
.controller('formController', function ($scope) {
// we will store all of our form data in this object
$scope.prefContactArray = [];
$scope.prefContactArray.push({ name: "Text", reply: "Great we'll text you.", isDefault: false });
$scope.prefContactArray.push({ name: "Email", reply: "Great we'll send you an email!", isDefault: false });
$scope.prefContactArray.push({ name: "Phone", reply: "Great we'll give you a call.", isDefault: false });
$scope.selectedprefContact = $scope.prefContactArray.name;
$scope.selectedprefContactReply = $scope.prefContactArray.reply;
$scope.fruitsList = [
{ id: 1, name: 'Apple', url: 'form/profile.html', state:'.profile' },
{ id: 2, name: 'Banana', url: 'form/interests.html', state:'.interests' },
{ id: 3, name: 'Guava', url: 'form/payment.html', state:'payment' }
];
$scope.selected = {
fruitsList: []
};
$scope.checkAll = function () {
$scope.selected.fruitsList = angular.copy($scope.fruitsList);
};
$scope.uncheckAll = function () {
$scope.selected.fruitsList = [];
};
$scope.create = function () {
var aTag = document.createElement('a ui-sref-active="active" ui-sref="fruitsList.state"
alt="fruitsList.name"');
status-buttons.appendChild(aTag);
$state.go($scope.selected.fruitsList.url);
};
$scope.formData = {};
$scope.submit = function downloadFile(fileName, urlData) {
var aLink = document.createElement('a');
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click");
aLink.download = fileName;
aLink.href = urlData;
aLink.dispatchEvent(evt);
}
var data = $scope.formData;
downloadFile('test.csv', 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data));
});
Form.html
<div id="form-container">
<div class="page-header text-center">
<h2>Let's Be Friends</h2>
<!-- the links to our nested states using relative paths -->
<!-- add the active class if the state matches our ui-sref -->
<div id="status-buttons" class="text-center">
<a ui-sref-active="active" ui-sref=".profile"><span>1</span> Profile</a>
<a ui-sref-active="active" ui-sref=".interests"><span>2</span> Interests</a>
<a ui-sref-active="active" ui-sref=".payment"><span>3</span> Payment</a>
</div>
</div>
<div id="splitscreen">
<!-- use ng-submit to catch the form submission and use our Angular function -->
<form id="signup-form" ng-submit="createQuote()">
<div id="userPanel" class="col-md-6" style="background-color:#999; z-index:2;">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" name="name" ng-model="formData.name">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" name="email" ng-model="formData.email">
</div>
<div class="form-group">
<label for="email">Phone</label>
<input type="text" class="form-control" name="email" ng-model="formData.phone">
</div>
<div class="form-group">
<label for="email">Website</label>
<input type="text" class="form-control" name="email" ng-model="formData.web">
</div>
<div ng-repeat="prefContact in prefContactArray">
<label>
<input type="radio" ng-value="prefContact.reply" ng-model="$parent.selectedprefContact" />
{{prefContact.name}}
</label>
</div>{{selectedprefContact | json}}
<div>
<label ng-repeat="fruit in fruitsList">
<input type="checkbox" checklist-model="selected.fruitsList" checklist-value="fruit.id"
ng-click="create()" /> {{fruit.name}}<br />
</label>
<button ng-click="checkAll()">Check all</button>
<button ng-click="uncheckAll()">Uncheck all</button> <br />
{{selected.fruitsList}}
</div>
</div>
</div>
<pre>
{{ formData }}
</pre>
<div id="questions" class="col-md-6">
<!-- our nested state views will be injected here -->
<div id="form-views" ui-view></div>
</div> </form>
</div>
</div>
<!-- show our formData as it is being typed -->
Submit Button Page
Thanks For Your Money!
<button type="submit" class="btn btn-danger">Submit</button> </div>
Index
<!-- CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootswatch/3.1.1/darkly/bootstrap.min.css">
<link rel="stylesheet" href="style.css">
<!-- JS -->
<!-- load angular, nganimate, and ui-router -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-animate.min.js"></script>
<script src="app.js"></script>
</head>
<!-- apply our angular app --> <body ng-app="formApp">
<div class="container col-md-12">
<!-- views will be injected here -->
<div class="col-md-12" ui-view></div>
</div>
In your create() function you use $state.go($scope.selected.fruitsList.url) which will change to the new state, however the value is the template path rather than the state path.
You should use $state.go($scope.selected.fruitsList.state) because the 'to' parameter of $state.go() should be the name of the state that will be transitioned to or a relative state path. If the path starts with ^ or . then it is relative, otherwise it is absolute.
$state
As #Andorov already mentioned, you need $state to navigate. UI Router has offers this service to make it easy for you to go from one state (or route, or page) to another. Add the dependency to your controller like so:
.controller('formController', function ($scope, $state) {
You are now able to say something like $state.go('form.payment') in your controller. This will navigate the person to the Payment form.
So all you would need to do now is when they submit the form (i.e. inside your $scope.createQuote() function which you haven't included in the code yet), find out what state you should go to and end with $state.go(stateToGoto).
Tip:
When I started out with UI router and AngularJs, I just made every route its own page, not using children. If you would do that you would get:
A route for your form
A route for every page it could go to.
Every route has its own controller, which makes it easy to put code in the right place. I don't like sharing the controller between children as it just makes it more difficult to understand which part of the code is for which child.
Does this help?
So, i have this main div, which wrapper the body code:
<main class="main">
</main>
And for which page i have a modifier with the name of the page:
Error page:
<main class="main main--error">
</main>
Events page:
<main class="main main--events">
</main>
Bookings page:
<main class="main main--bookings">
</main>
The main is always in the layout.html, because it will always be present on every page. How can i dynamically insert the class correspondent?
Should i create a MainController just for that? It has some clever way to do this without having to create a controller for just that?
UPDATE --
This is my layout.html:
<main class="main">
<section class="navbar" ng-controller="NavbarCtrl" ng-hide="pageName('Erro')">
<div class="navbar__container">
<div class="navbar__header">
<button class="navbar__toggle navbar__toggle--dashboard" data-sidebar-to="true">
<span class="navbar__icon-bar"></span>
<span class="navbar__icon-bar"></span>
<span class="navbar__icon-bar"></span>
</button>
<form class="navbar__form navbar__form--dashboard" role="search">
<input
class="navbar__search hide--mobile"
type="text" placeholder="Procure por algo...">
</form>
</div>
<ul class="navbar__list">
<li class="navbar__item">
<a class="navbar__link" href="#" title="logout">
<i class="icon icon__sign-out"></i>
<span class="hide--mobile"> Sair </span>
</a>
</li>
</ul>
</div>
</section>
<div
class="breadcrumb"
ng-controller="BreadcrumbCtrl"
ng-hide="pageName('Painel') || pageName('Erro')">
<div class="breadcrumb__container">
<h2 class="breadcrumb__title"> {{ $state.current.name }} </h2>
<ol class="breadcrumb__list">
<li class="breadcrumb__item breadcrumb__item--root"> Painel </li>
<li class="breadcrumb__item"> {{ $state.current.name }} </li>
</ol>
</div>
</div>
<div ui-view></div>
</main>
In the div with the ui-view, the Angular will append my pages, i have 3: error.tmpl.html, events.tmpl.html and bookings.tmpl.html
My generic app.js, where i register the angular module in the routes:
'use strict';
var adminApp = function($stateProvider, $urlRouterProvider) {
/*
* Booking Route
*
*/
var BookingCtrl = {
url: '/',
templateUrl: '/admin/booking/booking.tmpl.html',
controller: 'BookingCtrl'
};
/*
* Event Route
*
*/
var EventCtrl = {
url: '/eventos',
templateUrl: '/admin/event/event.tmpl.html',
controller: 'EventCtrl'
};
/*
* Error Route
*
*/
var ErrorCtrl = {
url: '/erro',
templateUrl: '/admin/error/505.tmpl.html',
controller: 'ErrorCtrl'
};
/*
* Register the states
*
*/
$stateProvider
.state('Bookings', BookingsCtrl)
.state('Eventos', EventCtrl)
.state('Error', ErrorCtrl);
$urlRouterProvider.otherwise('/');
};
angular.module('adminApp', [
'ui.router'
]).config(adminApp);
In my html templates i have this tiny html, which will be append in the ui-view.
The problem is that i need change the , if i access the error page, i need add the class 'main--error', if i access the events page, i need add the class 'main--events' and if i access the bookings page, i need add the class 'main--bookings'
It is conventional to reserve custom elements and attributes to angular directives. The directive will automatically add main class to top-level <main> and main main--statename to the elements belonging to view templates.
app.directive('main', function () {
return {
link: function (scope, element, attrs) {
var view = element.inheritedData('$uiView');
element.addClass('main');
if (!view) return;
element.addClass('main--' + view.state.name.toLowerCase());
}
};
});
Though it looks overengineered to me, there's nothing wrong with writing some classes by hand. If there's too much work with that maybe it is better to change the way the styles are organized.
Looked like directive 'main' will be inside other controllers, so the ng-class variable would be placed there.
In your Main Bookings controller:
$scope.class.name = 'main--bookings';
In your HTML
<main class="main" ng-class="class.name"></main>
I have a controller (called "catalogueController") that manages my search box and my search page. I have the controller initially set the page to automatically call the search function defined in "catalogueController" when the app loads to pre-load my array of items (called Inventory) to be repeated via ng-repeat in the page.
The process runs like this:
1. I submit the search form.
2. "catalogueController" will send the search term to my factory (called "Search").
3. "Search" will have a function which will make a server call to query my database for that particular search.
4. The database will send the results of the search to the "Search" factory.
5. The "Search" factory will send the results to the "catalogueController" controller.
6. "catalogueController" will update the $scope.Inventory to be equal to the new result that I was received.
My problem is that ng-repeat does not refresh itself to display my new and updated $scope.Inventory array. $scope.Inventory definitely is updated (I have made sure of this through various console logs).
I have also tried to use $scope.$apply(). It did not work for me.
Thank you in advance for your help!
Here is my code:
HTML Template
<form role="search" class="navbar-form navbar-left" ng-controller="catalogueController" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<main ng-view></main>
catalogue.html partial
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
"app.js" Angular App
var myApp = angular.module('qcbApp', ['ngRoute', 'ngCookies', 'appControllers']);
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'html/partials/login.html',
controller: 'registrationController'
}).
when('/sign-up', {
templateUrl: 'html/partials/sign-up.html',
controller: 'registrationController'
}).
when('/catalogue', {
templateUrl: 'html/partials/catalogue.html',
controller: 'catalogueController'
}).
when('/', {
templateUrl: 'html/partials/qcbhome.html'
}).
otherwise({
redirectTo: '/'
});
}]);
"catalogueController" Controller
myApp.controller('catalogueController', ['$scope', 'Search', function($scope, Search) {
var time = 0;
var searchCatalogue = function(search) {
$scope.inventory = null;
console.log("Controller -- "+search);
Search.searchCatalogue(search)
.then(function(results) {
console.log(results);
$scope.inventory = results;
});
};
if(time == 0)
{
searchCatalogue('');
time++;
}
$scope.search = function(term) {
searchCatalogue(term);
}
}]);
"Search" Factory
myApp.factory('Search', ['$http', '$q', function($http, $q) {
function searchCatalogue(term) {
var deferred = $q.defer();
console.log("Factory -- "+term);
$http.post('/catalogue_data', {term: term}, {headers: {'Content-Type': 'application/json'}})
.success(function(result) {
console.log(result[0].SKU);
deferred.resolve(result);
console.log("Factory results -- "+result);
});
return deferred.promise;
}
return {
searchCatalogue: searchCatalogue
}; //return
}]);
I think the problem is the ng-repeat can not access the inventory in scope. You have to create a div which contains both the form and the ng-repeat.
The html should be:
<div ng-controller="catalogueController">
<!-- Move the controller from the form to parent div -->
<form role="search" class="navbar-form navbar-left" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
</div>
I've seen the situation a few times where when you are updating a property directly on the $scope object there are interesting problems around databinding to that value (such as inventory). However if you databind to an object property of an object then the databinding works as expected. So for example use a property on $scope. I believe this is a copy by value vs copy by reference issue.
Update all your inventory references as follows
$scope.data.inventory = result;
Also don't forget to update your inventory reference in the html template:
<div ng-repeat="items in data.inventory" class="row-fluid">
Update: I made this plunk to figure it out - http://plnkr.co/edit/0ZLagR?p=preview
I think the primary problem is you have the controller specified twice. I removed it from the form and it started working.