On my web application, I have a menu on the left that should be the same on all pages. On top of that, whenever a page changes, the item that was selected should be selected again when the new page loads. For this I made a directive:
menu.html
<div id="sidebar-wrapper" ng-style="style()" fill-height="20">
<ul class="sidebar-nav">
<li class="component-menu" ng-repeat="component in menu.components">
<span>
<img src="img/arrow_down_ok.png" />
<span ng-class="{selected: menu.isActive(component.name)}" ng-click="menu.select(component.name,'all')" >{{ component.name }}</span>
</span>
<ul ng-if="component.devices">
<li class="device-menu" ng-repeat="device in component.devices">
<span ng-class="{selected: menu.isActive(component.name, device.name)}" ng-click="menu.select(component.name,device.name)">{{ device.name }}</span>
</li>
</ul>
</li>
</ul>
</div>
menu directive
var app = angular.module("myApp.directives", ["services"]);
app.directive("componentMenu", ["menuService", function() {
return {
restrict: "E",
templateUrl: "js/templates/menu.html",
controller: function($scope, $location, menuService) {
var that = this;
this.components = [ {
name : "pms",
devices : [ {name : "robot"}, {name : "controller"} ]
}, {
name : "bms",
devices : [ {name : "ScanningController"}, {name : "nozzle"} ]
}, ];
console.log("menu components:", this.components);
menuService.selectedComponent = "";
menuService.selectedDevice = "";
this.select = function(component, device) {
device = device || "all";
$location.search("component", component);
$location.search("device", device);
menuService.selectedComponent = component;
menuService.selectedDevice = device;
console.log("Menu selected:", menuService.selectedComponent + "/" + menuService.selectedDevice);
}
this.isActive = function(component, device) {
device = device || "all";
return component == menuService.selectedComponent && device == menuService.selectedDevice;
}
$scope.$watch(function($scope, that) {
return $location.url();
}, function(url) {
if (url) {
var component = menuService.selectedComponent;
var device = menuService.selectedDevice;
if (!(menuService.selectedComponent == component && menuService.selectedDevice == device)) {
that.select(component, device);
}
}
}
);
},
controllerAs: "menu",
};
}]);
menu service
var app = angular.module("myApp.services", []);
app.factory("menuService", [function() {
this._selectedComponent;
this._selectedDevice;
var menuSelection = {
selectedComponent: this._selectedComponent,
selectedDevice: this._selectedDevice
}
return menuSelection;
}]);
Whenever an item in the menu is selected, the console prints out the selected item correctly and next prints out the menu components (which seems correct since the URL gets changed). But when the page of the new URL is loaded, the variables inside the menu service are empty again.
Could anybody explain me why this is, as I understood that services were singletons and should keep their values.
When using service, it is instanciated with the new keyword which is why you bind the properties to the this and return a constructor. However you are using a factory. In that case you create an object that you return. Therefore you need your components to be instanciated with var =.
Related
I am trying to implement a accordion using angularfire . I am able to retrieve the top level list ("A1","D1","R1")for display but I am unable to figure out how to retrieve the child for each accordion tab that is selected. For Eg if I select "D1", it should open up and display "C1", "H1".
Here is my data on firebase
{
"A1" : {
"B1" : 50
},
"D1" : {
"C1 " : 98,
"H1" : 12
},
"R1" : {
"RR1" : 67,
"RD1" : 54
}
}
My code
var app=angular.module("sampleApp",["firebase"]);
app.controller("MyCtrl", ["$scope", "$firebaseArray", "$firebaseObject",
function($scope, $firebaseArray,$firebaseObject) {
var ref = firebase.database().ref("Products/");
var list = $firebaseArray(ref);
$scope.list = list;
$scope.activeTabs = [];
// check if the tab is active
$scope.isOpenTab = function (tab) {
// check if this tab is already in the activeTabs array
if ($scope.activeTabs.indexOf(tab) > -1) {
// if so, return true
return true;
} else {
// if not, return false
return false;
}
}
// function to 'open' a tab
$scope.openTab = function (tab) {
// check if tab is already open
if ($scope.isOpenTab(tab)) {
//if it is, remove it from the activeTabs array
$scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
} else {
// if it's not, add it!
$scope.activeTabs.push(tab);
}
}
}
]);
HTML Code
<div class="container accordion__container" ng-controller="MyCtrl">
<div class="accordion__tab" ng-repeat="products in list">
<div class="accordion__tab-title" ng-click="openTab(products.$id)">{{products.$id}} </div>
<div class="accordion__tab-content" ng-show="isOpenTab(products.$id)">
<div class="accordion__tab-contentdet" ng-repeat="productDet in <sub Product details>">
</div>
</div>
</div>
</div>
I made some changes in your code.
In HTML i used nav tabs.
<ul class="nav nav-tabs">
<li ng-repeat="products in list">
<a data-toggle="tab" href="#{{products.id}}">{{products.id}}</a>
</li>
</ul>
<div class="tab-content">
<div id="{{products.id}}" class="tab-pane fade" ng-repeat="products in list">
<h3>{{products.id}}</h3>
<p>Content : {{products.data}}.</p>
</div>
</div>
Controller
app.controller("MyCtrl", ["$scope", "$firebaseObject",
function($scope, $firebaseObject) {
var ref = firebase.database().ref("Products");
var list = $firebaseObject(ref);
list.$loaded().then(function() {
$scope.list = [];
angular.forEach(list, function(value,key){
$scope.list.push({ id: key, data: value})
})
});
}
]);
Another Method
Instead of using list.$loaded() you can use the below code:
ref.once('value', function(snapshot) {
$scope.list = [];
angular.forEach(snapshot.val(), function(value,key){
$scope.list.push({ id: key, data: value})
})
})
I just created a plunker for you. Please check it
https://plnkr.co/edit/5dOr7xAWIFlmdykAC1yh
if you have any doubt please let me know.
I am building a web application for our customer support. One of the needs is to be able to keep multiple tickets opened at the same time.
I was able to do the first part easily using a tabulation system and UI-Router.
However, with my current implementation, each time I change active tab, the previously-current tab is unloaded, and the now-current tab is loaded (because it was unloaded with a previous tab change).
This is not at all the expected behavior. I've already spent a couple of days trying to find a way to achieve this, without any luck.
The closest thing I was able to do is to use the multiple views system from UI-Router, but I need multiple instance of the same view to keep in memory (if multiple tickets are opened, they all are on the same view, with the same controller, but a different scope)
Here's my current implementation:
supportApp.js:
var app = angular.module("supportApp", ["ui.router", "ui.bootstrap"]);
app.config(function($stateProvider, $urlRouterProvider, $httpProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.decorator('d', function(state, parent){
state.templateUrl = generateTemplateUrl(state.self.templateUrl);
return state;
})
.state("main", {
abtract: true,
templateUrl: "main.html",
controller: "mainController"
})
.state("main.inbox", {
url: "/",
templateUrl: "inbox.html",
controller: "inboxController"
})
.state('main.viewTicket', {
url: '/ticket/{id:int}',
templateUrl: "viewTicket.html",
controller: "ticketController"
})
;
});
mainController.js: (handles other stuff, minimal code here)
app.controller("mainController", function($rootScope, $http, $scope, $state, $interval){
// Tabs system
$scope.tabs = [
{ heading: "Tickets", route:"main.inbox", active:false, params:{} }
];
var addTabDefault = {
heading: '',
route: null,
active: false,
params: null,
closeable: false
};
$rootScope.addTab = function(options){
if(!options.hasOwnProperty('route') || !options.route)
{
throw "Route is required";
}
var tabAlreadyAdded = false;
for(var i in $scope.tabs)
{
var tab = $scope.tabs[i];
if(tab.route == options.route && angular.equals(tab.params, options.params))
{
tabAlreadyAdded = true;
break;
}
}
if(!tabAlreadyAdded)
{
$scope.tabs.push($.extend({}, addTabDefault, options));
}
if(options.hasOwnProperty('active') && options.active === true)
{
$state.go(options.route, options.hasOwnProperty('params')?options.params:null);
}
};
$scope.removeTab = function($event, tab){
$event.preventDefault();
if($scope.active(tab.route, tab.params))
{
$scope.go($scope.tabs[0].route, $scope.tabs[0].params);
}
$scope.tabs.splice($scope.tabs.indexOf(tab), 1);
};
$scope.go = function(route, params){
$state.go(route, params);
};
$scope.active = function(route, params){
return $state.is(route, params);
};
$scope.$on("$stateChangeSuccess", function() {
$scope.tabs.forEach(function(tab) {
tab.active = $scope.active(tab.route, tab.params);
});
});
});
main.html:
<div class="container-fluid" id="sav-container">
<div class="row-fluid">
<div class="col-lg-2">
<form role="form" id="searchForm" action="#">
<div class="form-group has-feedback">
<input class="form-control" type="search" />
<span class="glyphicon glyphicon-search form-control-feedback"></span>
</div>
</form>
</div>
<div class="col-lg-10" id="support_main_menu">
<ul class="nav nav-tabs">
<li ng-repeat="t in tabs" ng-click="go(t.route, t.params)" ng-class="{active: t.active, closeable: t.closeable}" style="max-width: calc((100% - 128px) / {{tabs.length}});">
<a href class="nav-tab-text">
<button ng-show="t.closeable" ng-click="removeTab($event, t)" class="close" type="button">×</button>
<span>{{t.heading}}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="row-fluid">
<div class="tab-content" ui-view></div>
</div>
</div>
It seems to me that what I ask is pretty standard, but I sadly couldn't find any usefull thing on the Internet
The basic idea is to store state (i.e. list of tickets) in a service as opposed to a controller. Services hang around for the life of the application. There are some articles on this. I'm still developing my approach but here is an example:
var RefereeRepository = function(resource)
{
this.resource = resource; // angular-resource
this.items = []; // cache of items i.e. tickets
this.findAll = function(reload)
{
if (!reload) return this.items;
return this.items = this.resource.findAll(); // Kicks off actual json request
};
this.findx = function(id)
{
return this.resource.find({ id: id }); // actual json query
};
this.find = function(id) // Uses local cache
{
var itemx = {};
// Needs refining
this.items.every(function(item) {
if (item.id !== id) return true;
itemx = item;
return false;
});
return itemx;
};
this.update = function(item)
{
return this.resource.update(item);
};
};
refereeComponent.factory('refereeRepository', ['$resource',
function($resource)
{
var resource =
$resource('/app_dev.php/referees/:id', { id: '#id' }, {
update: {method: 'PUT'},
findAll: {
method: 'GET' ,
isArray:true,
transformResponse: function(data)
{
var items = angular.fromJson(data);
var referees = [];
items.forEach(function(item) {
var referee = new Referee(item); // Convert json to my object
referees.push(referee);
});
return referees;
}
},
find: {
method: 'GET',
transformResponse: function(data)
{
var item = angular.fromJson(data);
return new Referee(item);
}
}
});
var refereeRepository = new RefereeRepository(resource);
// Load items when service is created
refereeRepository.findAll(true);
return refereeRepository;
}]);
So basically we made a refereeRepository service that queries the web server for a list of referees and then caches the result. The controller would then use the cache.
refereeComponent.controller('RefereeListController',
['$scope', 'refereeRepository',
function($scope, refereeRepository)
{
$scope.referees = refereeRepository.findAll();
}
]);
I have a side menu directive that populates itself from a Sidebar Controller but the routing for all click events on the entire website comes from the page's parent Route Controller.
The Container main page
<body data-ng-controller ="RouteCtrl as vm">
<div data-header=""></div>
<div data-ng-controller="SidebarCtrl">
<div data-side-bar=""></div>
</div>
<div data-ng-view=""></div>
......
</body>
The SidebarCtrl
(function () {
'use strict';
var controllerId = 'SidebarCtrl';
angular.module('app').controller(controllerId,
['$scope', SidebarCtrl]);
function SidebarCtrl($scope) {
var vm = this;
vm.title = 'SidebarCtrl';
vm.toggleMenu = function () {
vm.minifyMe = !vm.minifyMe;
};
vm.expandNav = function (item) {
if (item !== vm.expandedItem) {
vm.expandedItem = item;
} else {
//vm.expandedItem = null;
}
};
vm.menuItems = [
{
name: "Parent", subMenu:
[
{ name: "Sub 1", module: "Mod1", url: "rootPage" },
{ name: "Sub 2", module: "Mod2", url: "mod2Sub1/somePage" },
{ name: "Sub 3", module: "Mod3", url: "folder2/mod3Sub2/somePage" },
{ name: "Sub 4", module: "Mod4", url: "folder3/subFoler/somePage" }
]
}
];
}
angular.module('app')
.directive('sideBar', function () {
return {
restrict: "A",
replace: true,
templateUrl: '/app/html/common/sidebar.html'
};
});
})();
The sidebar html
<aside id="left-panel" data-ng-controller="SidebarCtrl as vm">
....
<li data-ng-repeat="menuItem in vm.menuItems" ....>
<span class="menu-item-parent">{{menuItem.name}}</span>
<ul ....>
<li data-ng-repeat="subItem in menuItem.subMenu">
<a style="cursor: pointer;" data-ng-click="vm.changeView('{{subItem.module}}','{{subItem.url}}')">{{subItem.name}}</a>
</li>
</ul>
</li>
....
</aside>
The Route Controller
// Route navigation for entire app
vm.changeView = function (moduleView, dashboardView) {
$rootScope.event = { viewUrl: dashboardView, moduleUrl: moduleView };
console.log(dashboardView);
$location.path(dashboardView);
};
var newRoute = $routeParams.primaryNav;
if ($routeParams.secondaryNav != "" && $routeParams.secondaryNav != undefined) {
newRoute = newRoute + '/' + $routeParams.secondaryNav;
}
if ($routeParams.tertiaryNav != "" && $routeParams.tertiaryNav != undefined) {
newRoute = newRoute + '/' + $routeParams.tertiaryNav;
}
$scope.templateUrl = '/app/html/' + newRoute + '.html';
As show above, the side bar populates but the routing does not work
If I made the sidebar nav static [which I do not want], and the sidebar page's controller the RouteCtrl the side bar navigation responds properly.
How can I maintain the entire navigation of the website in the route controller, yet decouple the various segments of the website, eg. the sidebar nav, the headernav, the footernav, etc ?
To solve this problem, I did several things:
I removed vm as a pattern, I cannot see any value in using it so I reverted back to the prior pattern of $scope.
Although the parent controller's function changeView is now accessible to the child [again, once ALL 'vm' is replaced with $scope in the project, I opted for $emit] :
1. Index.html
<body data-ng-controller ="RouteCtrl">
<div data-header=""></div>
<div data-ng-controller="SidebarCtrl">
<div data-side-bar=""></div>
</div>
<div data-ng-view=""></div>
......
</body>
2. Sidebar.html
< a style="cursor: pointer;" data-ng-click="handleNav(subItem.module,subItem.url)">{{subItem.name}}</a>
3. Side Crtl:
$scope.handleNav = function (args1, args2) {
$scope.$emit('handleNewView', (args1, args2));
};
4. Parent Crtl:
$scope.$on('handleNewView', function (args1, args2) {
$scope.changeView(args1, args2);
});
$scope.changeView = function (moduleView, dashboardView) {
$rootScope.event = { viewUrl: dashboardView, moduleUrl: moduleView };
$location.path(dashboardView);
};
In conclusion, I cannot see any value in adopting "Controller as vm" as a pattern.
I could not get inplace edit working within a tree.
I forked a fiddle where inplace edit worked within a simple array of input data
Here is the fiddle
http://jsfiddle.net/cguy/wcMzw/8/
thanks for any help.
<script type="text/ng-template" id="tree_item_renderer.html">
<button ng-click="expand_collapse(data)" ng-show="data.show && data.nodes.length > 0">-</button>
<button ng-click="expand_collapse(data)" ng-show="!data.show && data.nodes.length > 0">+</button>
<edit-in-place value="data.name"></edit-in-place>
<ol ng-show="data.show">
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'"></li>
</ol>
</script>
<div id="tree">
<ol ng-controller="TreeCtrl" >
<li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
</ol>
</div>
var app = angular.module( 'myApp', [] );
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});
app.controller("TreeCtrl",['$scope', function($scope) {
$scope.expand_collapse = function(data) {
data.show = !data.show;
}
// below is an array of size 1 - it does not have to be that way
$scope.tree = [ {
name : "Root",
show : true,
nodes : []
} ];
var nodeChild1 = {
name : "Child 1",
show : false,
nodes : []
};
var nodeChild2 = {
name : "Child 2",
show : false,
nodes : []
};
// Add the children
$scope.tree[0].nodes.push(nodeChild1);
$scope.tree[0].nodes.push(nodeChild2);
var nodeGrandChild1 = {
name : "Grand Child 1",
show : false,
nodes : []
};
var nodeGrandChild11 = {
name : "Grand Child 11",
show : false,
nodes : []
};
nodeChild1.nodes.push(nodeGrandChild1);
nodeChild1.nodes.push(nodeGrandChild11);
var nodeGrandChild2 = {
name : "Grand Child 2",
show : false,
nodes : []
};
var nodeGrandChild21 = {
name : "Grand Child 21",
show : false,
nodes : []
};
nodeChild2.nodes.push(nodeGrandChild2);
nodeChild2.nodes.push(nodeGrandChild21);
} ]);
There were some extra Controller tags in my original fiddle.
Now it works - here is the updated fiddle.
http://jsfiddle.net/cguy/wcMzw/9/
<br />
<p>Here we repeat the contacts to ensure bindings work
</p>
<script type="text/ng-template" id="tree_item_renderer2.html">
<button ng-click="expand_collapse(data)" ng-show="data.show && data.nodes.length > 0">-</button>
<button ng-click="expand_collapse(data)" ng-show="!data.show && data.nodes.length > 0">+</button>
{{data.name}}
<ol ng-show="data.show">
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer2.html'"></li>
</ol>
</script>
<div id="tree2">
<ol>
<li ng-repeat="data in tree" ng-include="'tree_item_renderer2.html'"></li>
</ol>
</div>
For the 2nd tree in your demo, you missed the directive
<edit-in-place value="data.name"></edit-in-place>
I currently have developed a table of content using AngularJS, the table will populate based on an Angular Service "Model" which invokes a web service and returns list and using ng-repeat and creating a table and all its content.
Everything at the moment works fine, I have a minor problem though. Part of the table, we are outputting an action button which when clicked invokes a web service which update the current record. Am trying to make the record data gets updated automatically, but i must refresh the page in order to see the changes.
Here is my code
My app.js
angular.module('my_vehicles', ['vehicleServices', 'AccountsDirectives']);
service.js
'use strict';
angular.module('vehicleServices', ['ngResource']).
factory('Car', function($resource) {
return $resource('/vehicle/api/car.json/:id', {},
{
query: {method:'GET', isArray:false},
delete: {method:'DELETE', isArray:false},
update: {method:'PUT', isArray:false}
}
);
});
controller.js
'use strict';
function MyVehicleController($scope, Car) {
var init = function() {
$scope.page_has_next = true;
$scope.cars = [];
$scope.page = 1;
};
// initialize values
init();
Car.query({my_vehicle: true},
// success
function(data) {
$scope.page_has_next = data.has_next;
$scope.cars = data.objects;
},
// error
function(data) {
}
);
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
});
}
$scope.delete = function(id, index) {
Car.delete(
{id: id},
// on success
function() {
// remove the element from cars array and it will be
// automatically updated by ng-repeat
$scope.cars.splice(index, 1);
$scope.loadMore(1);
}
);
}
$scope.is_total_zero = function() {
return !!($scope.cars.length)
//return $scope.cars.length > 0 ? true : false
}
$scope.loadMore = function(limit) {
if($scope.page_has_next) {
$scope.$broadcast('loading_started');
console.log(limit);
Car.query({my_vehicle: true, page: $scope.page, limit: limit},
// success
function(data) {
$scope.page_has_next = data.has_next;
$scope.cars = $scope.cars.concat(angular.fromJson(data.objects));
$scope.page++;
$scope.$broadcast('loading_ended');
},
// error
function() {
$scope.page_has_next = false;
$scope.$broadcast('loading_ended');
}
);
}
}
$scope.$on('loading_started', function() {
$scope.state = 'loading';
});
$scope.$on('loading_ended', function() {
$scope.state = 'ready';
});
}
and finally, my html code
<tr ng-repeat="car in cars">
<td>{% ng car._get_model_display.make_display %} {% ng car._get_model_display.model_display %} {% ng car._get_model_display.trim_display %}</td>
<td>{% ng car.created_at_format %}</td>
<td>{% ng car.view_count %}</td>
<td ng-model="car.status_label">{% ng car.status_label %}</td>
<td>
<div class="btn-group">
<button ng-disabled="car.status == 'S' || car.status == 'P'" ng-model="edit" class="btn btn-mini edit-btn">{% trans 'Edit' %}</button>
<button ng-disabled="car.status == 'S'" ng-click="delete(car.id, $index)" class="btn btn-mini delete-btn">{% trans 'Delete' %}</button>
<button ng-disabled="car.status == 'S' || car.status == 'P'" ng-click="mark_sold(car.id, $index)" class="btn btn-mini edit-btn">{% trans 'Mark as sold' %}</button>
</div>
</td>
</tr>
P.S the {% ng XXX %} is outputting {{ XXX }}, am using the above syntax because django templating engine does not allow me to use {{}} so i've developed a templatetag that would output {{}} ..
As mentioned earlier, my problem is that every time I invoke "mark as sold" it would invoke the cars.update() but it will not update the record displayed, must refresh to see changes. Any idea how i can solve this?
As far as I understand your code you only update the db without updating the cars model ($scope.cars) so changes are only reflected in the db but not in the AngularJS application.
Maybe try the following:
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
$scope.cars[id].status = 'S';
});
}
You need to also update your in-memory cars array.
You already have the array index (second parameter of the mark_sold function):
$scope.mark_sold = function(id, index) {
Car.update({
id : id,
status : 'S'
},
function(data) {
// Update in-memory array
$scope.$apply(function(scope) {
scope.cars[index].status = 'S';
});
});
}