Here's the demo:
http://www.tanadsplinare.com.hr/tmp/ngtest/ngVer/
(enter 1, 2 or 3 in search field then click on any item.)
Now the item details display in the itemDetail div on the right side.
However, the ui-view is placed in child partial view, in the left panel under the list, it is itemsListDetailOutput div.
I don't know other way, so I quickly copy the innerHTML of the ui-view to the itemDetail (where I want to display the item detail, but I can't apply ui-view to it because there is already other ui-view there and the item list uses it) and clear the innerHTML of the ui-view. I guess I should do it on digest event, but I don't know how to do it, so I just used setTimeout:
$('#itemDetail').hide();
setTimeout(function() {
$("#itemDetail").html($("#itemsListDetailOutput").html()).fadeIn(200);
}, 10);
So basically:
1) is there other way to do it, the right way, instead of moving inner HTML to desired HTML element; and if there isn't:
2) how can I catch digest event to do it, instead of using setTimeout?
Thank you
EDIT:
I've posted it to plunker: http://plnkr.co/edit/EC4FBr9RtTvaycBteLJ3 , have no idea why it doesn't work there, but the code is there. I'll post it here also:
index.html:
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="stylesheet" href="css/app.css"/>
<script src="lib/jquery-2.0.1.min.js" type="text/javascript"></script>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="lib/angular/angular-ui-router.min.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/services.js" type="text/javascript"></script>
<script src="js/controllers.js" type="text/javascript"></script>
</head>
<body>
<nav id="menu">
Angular way
JS way
</nav>
<section>
<div id="contentWrapper">
<nav class="itemsListHolder" ng-controller="ItemsListHolderController">
<input ng-model="search" ng-change="searchUpdated(search)" type="search" id="search" placeholder="Search..." />
<div ui-view id="itemsList"></div>
enter 1, 2 or 3
</nav>
<div id="itemDetail" class="panel">Select the film from the list...</div>
</div>
</section>
<div id="preloader"></div>
</body>
</html>
/partials/ngItemsList.html:
<section ng-repeat="item in items">
<div class="itemsListItem" data-index="{{item.id}}" ng-click="displayDetail(item.id)">
<div class="dataTitleOriginal">{{item.titleOriginal}}</div>
<div class="dataTitleLocal">{{item.titleLocal}}</div>
<div class="dataYear">{{item.year}}</div>
</div>
</section>
<div ui-view id="itemsListDetailOutput"></div>
/partials/ngItemDetail.html:
<div class="itemDetailName">Original Title</div>
<div class="itemDetailValue titleOriginal">{{titleOriginal}}</div>
<div class="itemDetailName">Local Title</div>
<div class="itemDetailValue titleLocal">{{titleLocal}}</div>
<div class="itemDetailName">Year</div>
<div class="itemDetailValue year">{{year}}</div>
/js/app.js:
'use strict';
angular.module('myApp', [
'myApp.controllers',
'ui.router'
])
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('itemsList', {
url: '/list/:query',
templateUrl: 'partials/ngItemsList.html',
controller: 'ItemListController'
})
.state('itemsList.itemDetail', {
url: '/detail/:id',
templateUrl: 'partials/ngItemDetail.html',
controller: 'ItemDetailController'
})
;
//$locationProvider.html5Mode(true);
} ])
.run(['$rootScope', '$state', 'Main', 'Items', function($rootScope, $state, Main, Items) {
Main.init();
Items.initDatabase();
$rootScope.$on('$stateChangeStart', function() {
document.getElementById("preloader").style.display = "block";
setTimeout(function() {
document.getElementById("preloader").style.display = "none";
}, 5000);
});
$rootScope.$on('$stateChangeSuccess', function() {
document.getElementById("preloader").style.display = "none";
});
} ])
;
/js/services.js:
'use strict';
angular.module('myApp')
.factory('Main', function() {
return {
testdata: null,
init: function() {
}
}
})
.factory('Items', function(Main) {
var insertItem = function(permalink, titleOriginal, titleLocal, year) {
var items = getItemsList();
items.push({
permalink: permalink,
titleOriginal: titleOriginal,
titleLocal: titleLocal,
year: year
});
Main.testdata = items;
};
var initDatabase = function() {
// Fill Database.
if (!Main.testdata) {
insertItem("1111-film", "1111 orig", "1111", "1980");
insertItem("3333-film", "3333 orig", "3333", "1982");
}
};
var getItemsList = function() {
return (Main.testdata) ? Main.testdata : [];
};
var getItem = function(index) {
var list = getItemsList();
if (list && list.length && list.length > index) {
return list[index];
}
};
return {
initDatabase: initDatabase,
insertItem: insertItem,
getItemsList: getItemsList,
getItem: getItem
}
})
;
/js/controllers.js:
'use strict';
angular.module('myApp.controllers', [])
.controller('ItemsListHolderController', ['$scope', '$state', function($scope, $state) {
$scope.searchUpdated = function(query) {
$state.go('itemsList', {
query: query
});
};
} ])
.controller('ItemListController', ['$scope', '$state', '$stateParams', 'Items', function($scope, $state, $stateParams, Items) {
var query = $stateParams.query;
var result = Items.getItemsList();
var filteredResult = [];
for (var i = 0; i < result.length; i++) {
if (result[i].titleOriginal.indexOf(query) > -1 || result[i].titleLocal.indexOf(query) > -1 || result[i].year.indexOf(query) > -1) {
result[i].id = i;
filteredResult.push(result[i]);
}
}
$scope.items = filteredResult;
$scope.displayDetail = function(index) {
$state.go('itemsList.itemDetail', {
id: index
});
};
} ])
.controller('ItemDetailController', ['$scope', '$stateParams', 'Items', function($scope, $stateParams, Items) {
var itemId = $stateParams.id;
var item = Items.getItem(itemId);
$scope.titleOriginal = item.titleOriginal;
$scope.titleLocal = item.titleLocal;
$scope.year = item.year;
//xxxx UGLY FIX: display in ui-view, (it's set to display none anyway) and copy its innerHTML to another DOM element; also wait a while till digests
$('#itemDetail').hide();
setTimeout(function() {
$("#itemDetail").html($("#itemsListDetailOutput").html()).fadeIn(200);
}, 10);
} ])
;
UPDATE
Here is detailed Nested View Example please check to understand nested views in ui.router...
ANSWER
ui.router supports nested view which you can use in this situation.
As you set details page as child state of list so you can put a ui-view in your list html and tell details state to include itself into that ui-view
syntax for nested views is a bit different than the normal state defination. Instead of directly define your templateUrl and controller you should define them in views property.
so firstly change your state defination with this one
views: {
uiViewName#stateName: {
templateUrl: '...',
controller: '...'
}
}
so removing templateUrl and controller from your defination and put them into views property and you should set your stateName as well it is itemsList in your example...
Now you should add a ui-view with the name uiViewName (you can change it whatever you want) in your list html
...
<div ui-view="uiViewName"></div>
...
so whenever you go details page it will be injected that specific ui-view...
Related
I want to make a sidebar with list item that can be dynamically changed based on the settings page.
My app request settings.json via factory() and then called it in a controller. The controller will be used by settings.html (ngView) and sidebar.html (ngInclude).
The json will return Boolean value that also can be changed on setting page that contain checkbox which return true if check and false if not checked. I use ngShow on the sidebar to display/hide the list items.
How can I made the sidebar to reflect the changes as I tick the checkbox?
settings.factory.js
var settingsFactory = angular.module('settingsFactory', []);
settingsFactory.factory('SettingsFilterFactory', ['$http', function ($http) {
var settingsFactory = {};
settingsFactory.getSettings = function () {
return $http.get('app/data/settings.json');
};
return settingsFactory;
}]);
controller
var settingsControllers = angular.module('settingsControllers', ['settingsFactory']);
settingsControllers.controller('SettingsFilterController', ['$scope', '$http', 'SettingsFilterFactory', function ($scope, $http, SettingsFilterFactory) {
$scope.settings;
$scope.status;
getSettings();
function getSettings() {
SettingsFilterFactory.getSettings()
.then(function (response) {
$scope.settings = response.data;
}, function (error) {
$scope.status = 'Unable to load: ' + error.message;
});
}
}]);
app.js
var app = angular.module('app', ['ngRoute', 'settingsControllers']);
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/settings', {
title: 'Settings',
templateUrl: 'app/components/settings/settings.html',
controller: 'SettingsFilterController'
}).
otherwise({
redirectTo: '/home'
});
}]);
My index.html is something like this:
...
<body>
<section class="sidebar">
<div ng-include="'app/components/sidebar/sidebar.html'"></div>
</section>
<section class="content">
<div ng-view></div>
</section>
</body>
...
sidebar.html
<ul class="sidebar-menu" ng-controller="SettingsFilterController">
<li ng-show"settings.hiddenMenu">This is secret link</li>
</ul>
settings.html
...
<div class="checkbox">
<input type="checkbox" ng-model="settings.hiddenMenu" ng-true-value=true ng-false-value=false> Check this to show hidden menu
</div>
...
Try something like this (untested):
settings.factory.js
var settingsFactory = angular.module('settingsFactory', []);
settingsFactory.factory('SettingsFilterFactory', ['$http', function ($http) {
var settingsFactory = {};
settingsFactory.getSettings = function () {
return $http.get('app/data/settings.json');
};
settingsFactory.hiddenMenu= true;
settingsFactory.someOtherSetting = {};
return settingsFactory;
}]);
sidebar controller
settingsControllers.controller('SidebarController', ['$scope', '$http', 'SettingsFilterFactory', function ($scope, $http, SettingsFilterFactory) {
//do this in each controller, so that the factory becomes a property of $scope and can be seen in the HTML
$scope.settingsFactory = SettingsFilterFactory;
}
sidebar.html
<ul class="sidebar-menu" ng-controller="SidebarController">
<li ng-show"settingsFactory.hiddenMenu">This is secret link</li>
</ul>
settings.html
...
<div class="checkbox">
<input type="checkbox" ng-model="settingsFactory.hiddenMenu" ng-true-value=true ng-false-value=false> Check this to show hidden menu
</div>
...
Essentially, you are binding the settingsFactory object which is a singleton to each $scope that is provided by each controller. Each controller is able to change the property on the factory object, which is then visible in all other controllers that have injected this object.
I am creating an Angular application in ASP.NET with partial templates. When I select the menu, and click "Employees", a controller calls a webservice and returns the correct JSON data, and I store it in $scope.message. I've displayed $scope.message on the screen, and everything works.
However, I want to feed $scope.message as data source to a D3plus boxplot graph stored in a partial template called employees.html. It does not seem to be working. Perhaps I am making an obvious mistake and would greatly appreciate the community's help. Thank you! Here is mu code:
Webform1.aspx (webform):
...
<body onload="loadPikaday()" data-ng-app="Demo">
<%-- Header --%>
<div id="topBanner" class="topBanner">
<div id="menu" class="menu">
Report from:
<input id="startDate" size="6" type="text" data-ng-model="startDate" /> To
<input id="endDate" size="6" type="text" data-ng-model="endDate" />
<%-- Dropdown menu --%>
<div class="dropdown">
<button onclick="myFunction()" class="dropbtn">MENU</button>
<div id="myDropdown" class="dropdown-content">
Cases
Protocols
Employees
</div>
</div>
</div>
</div>
<%-- Where html is injected--%>
<div data-ng-view=""></div>
</body>
...
myScript.js (Angular module):
/// <reference path="angular.min.js" />
var app = angular.module("Demo", ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/cases', { // Root: initialize with cases
templateUrl: 'templates/cases.html',
controller: 'casesController'
})
.when('/protocols', { // Root: initialize with cases
templateUrl: 'templates/protocols.html',
controller: 'protocolsController'
})
.when('/employees', {
templateUrl: 'templates/employees.html',
controller: 'employeesController'
})
})
.controller('casesController', function ($scope) {
$scope.message = "Cases!";
})
.controller('protocolsController', function ($scope) {
$scope.message = "This is the protocols page!";
})
.controller('employeesController', function ($scope, $http) {
$http.get('dataWebService.asmx/getTotalForDateIntervalEmployees', {
params: { startDate: '2015-01-01', endDate: '2016-08-01' }
})
.then(function (response) {
$scope.message = response.data;
})
});
employees.html (injected partial template):
<!doctype html>
<meta charset="utf-8">
<script src="//d3plus.org/js/d3.js"></script>
<script src="//d3plus.org/js/d3plus.js"></script>
<div id="viz"></div>
<script>
var data = {{message}};
var visualization = d3plus.viz()
.container("#viz")
.data(data)
.type("box")
.id("name")
.x("building")
.y("total")
.time(false)
.ui([{
"label": "Visualization Type",
"method": "type",
"value": ["scatter", "box"]
}])
.draw()
</script>
Here is a simple example using a directive...
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope, $http) {
$scope.data = [];
$http.get('/data').then(function(data) {
$scope.data = data.data;
});
});
app.directive('chart', function() {
return {
restrict: 'EA',
scope: {
data: '=',
},
link: function(scope, elem, atts) {
var svg = d3.select(elem[0]).append('svg'),
chart = svg.append('g');
scope.$watch('data', function(data) {
chart.selectAll('*').remove();
if (data.length) {
updateChartData(data);
}
});
function updateChartData(data) {
// add all your d3 code here...
}
}
};
});
Then you can bind the data with something like
<chart data="data"></chart>
You're going about this all wrong, I'm afraid to say. You should not have javascript in your template and then use angular to inject values into that script. Angular is based on the MVC principle and you are mixing up controller and view here. I'd strongly suggest using an angular directive to wrap your d3 functionality: like for example this: https://github.com/mariohmol/angular-d3plus. Simple principle here: never, ever have js code in templates in Angular.
I'm new to angular js. I was trying to save and share the scope of two different controllers.
Below is my script and views.
script.js:
var app = angular.module('app', ['ngRoute']);
app.run(function ($rootScope) {
$rootScope.$on('scope.stored', function (event, data) {
console.log("scope.stored", data);
});
});
app.controller('FirstController', function($scope) {
$scope.counter = 0;
$scope.add = function(amount) { $scope.counter += amount; };
$scope.subtract = function(amount) { $scope.counter -= amount; };
});
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/v1', {
templateUrl: 'views/v1.ejs',
controller: 'controller1'
}).
when('/v2', {
templateUrl: 'views/v2.ejs',
controller: 'controller2'
})
}]);
app.controller('controller1', function($scope,Scopes) {
Scopes.store('controller1',$scope);
$scope.data = "controller1";
$scope.buttonClick = function () {
console.log( Scopes.get('controller2').data);
};
});
app.controller('controller2', function($scope,Scopes) {
Scopes.store('controller2',$scope);
$scope.data = "controller2";
$scope.buttonClick = function () {
console.log( Scopes.get('controller1').data);
};
});
app.factory('Scopes', function ($rootScope) {
var mem = {};
return {
store: function (key, value) {
$rootScope.$emit('scope.stored', key);
mem[key] = value;
},
get: function (key) {
return mem[key];
}
};
});
index.html:
<!doctype html>
<html ng-app="app">
<head>
<script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/script.js"> </script>
</head>
<body>
<a href='#v1'>View1</a>
<a href='#v2'>View2</a>
<div ng-view>
</div>
</body>
</html>
v1.ejs:
<div ng-controller="controller1">
{{data}}
<br>
<button ng-click="buttonClick()">Clickme!</button>
</div>
v2.ejs:
<div ng-controller="controller2">
{{data}}
<br>
<button ng-click="buttonClick()">Clickme!</button>
</div>
The problem is:
Whenever the page is loaded, #/v1 is triggered.
If i click on 'Clickme', JUST AFTER THE PAGE LOADS, Error: Scopes.get(...) is undefined is fired.
But, if i navigate to #/v2 and then to #/v1, the scopes are getting stored and the error is NOT fired.
Is there is some problem with with routeProvider? All the controllers wont be loaded at on-load event?
Please help me on this.
Any ideas how to build solution, that helps generate url in view in more convenient way, instead of hard-coding like that:
<a ng-href="#/Book/{{item.bookId}}/ch/{{item.id}}">Chapter {{item.id}}</a>
I want to use:
<a chapters="[item.bookId, item.id]">Chapter {{item.id}}</a>
So it checks routes and generates for each route specific directive.
I'm interested in the most generic solution as possible.
I would highly recommend you make use of ui-router, and its $stateProvider.
var app = angular.module('yourModuleName', ['ui.router']);
app.config(function ($stateProvider) {
$stateProvider
.state('book', {
url: '/Book/:bookId'
})
.state('book.chapter', {
url: '/ch/:id'
});
});
<a ui-sref="book.chapter({bookId: item.bookId, id: item.id})">Chapter {{item.id}}</a>
Something along those lines should do the trick. I'm not at all familiar with the other parameters of your application, but building dynamic URL's with passed in parameters to match up with a $state is a breeze.
ui-router: https://github.com/angular-ui/ui-router
ui-sref (directive): https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-sref
First of all you can use Angular-UI
Angular-UI/UI-Router
The main idea in there is to have states because you have single page app and everything are just state who renders in single place. No refresh nothing.
When integrating it you can create
$stateProvider
.state("bookPreview", {
url: "/book/:id/:itemId",
controller: "BookPreviewCtrl",
templateUrl: 'sb/add-cpe-template.tpl.html'
});
In your html you can do the following thing:
<button ng-click="view(item.bookId, item.id)">Chapter {{item.id}}</button>
or something like that, you can assign ng-click on hyperlinks as well.
The java script view function is:(but before you must inject $
controller("BookSelectionCtrl",function($scope,$state){
//this will rewrite the url , go to the state and load the template(view).
$scope.view=function(bookId,itemId){
$state.go("bookPreview",{id:bookId,itemId:itemId}) //there is a third object representing options, which are optional and you can check them on doc site
}
})
controller("BookPreviewCtrl",function($scope,$stateParams){
//in this new ctrl of the new view you can now do what ever you want with these params
$scope.bookId = $stateParams.id;
$scope.itemId = $stateParams.itemId;
})
You need to loop over all routes and build directives dynamically, here is a start http://plnkr.co/edit/d592a58CMan5BVKQYSAy?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$route) {
var keys = [];
for(var k in $route.routes) keys.push(k);
$scope.name = keys;
for (var i=0; i<keys.length; ++i) {
app.directive('SomethingDynamic', function() {
return {
restrict: 'A',
replace: true,
template: '....',
};
});
}
});
app.config(function ($routeProvider) {
$routeProvider.
when('/complex/:name/:color/:number/extra', {
templateUrl: "404.html",
name:'complex'
}).
when('/objects', {
templateUrl: "404.html",
name:'list'
}).
when('/object/detail/:id', {
templateUrl: "404.html",
name:'detail'
});
});
Or you can create directive (jsbin):
View:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body ng-app="app">
<div ng-controller="mainCtrl">
<book-link book="book" ng-repeat="book in books"></book-link>
</div>
</body>
</html>
JS:
var app = angular.module("app",[])
.controller("mainCtrl", function($scope){
var books = [
{bookId : "book1", id:"1" },
{bookId : "book1", id:"2" },
{bookId : "book1", id:"3" }
];
$scope.books = books;
})
.directive("bookLink",function(){
return {
restrict:"E",
scope:{book:'='},
template:"<a href='/book/{{book.bookId}}/ch/{{book.id}}'>Chapter {{book.id}}</a><br/>"
};
});
This week I implemented swiping on a mobile app and found no clear demo showing swiping in both directions. So I figured to give back to the community a little I would write one. That was until I noticed some strange behavior I couldn't understand.
I put two buttons up in the demo to test drive the demo. To swipe you can use your mouse next to the text. What happens is that when I load up the demo and swipe the already existing ngView gets the class from slideDir, the new one however does not. This happens because the new view has a new scope and has no idea what slideDir is supposed to be.
Case two is when I press a button before swiping, there is a slideDir created and filled in the scope the buttons are using and therefore swiping starts to almost behave like it should(other then some synch issues between two scopes).
So what in my configuration am I doing wrong? I had the assumption that because the app controller was on a higher laying div this was not re-initiated each time a new ngView was loaded.
The following url contains the demo: http://blackunknown.com/demos/swipingdemo/demo.html
Here is my set up in the body tag:
<div ng-app="app" ng-controller="AppCtrl">
<button ng-click='swipeLeft()'>Swipe Left</button>
<button ng-click='swipeRight()'>Swipe Right</button>
<div class="slide" ng-view ng-class="slideDir" ng-swipe-left="swipeLeft()" ng-swipe-right="swipeRight()">
</div>
<!-- Templates loaded on url change -->
<script type="text/ng-template" id="pg1.html">
<H3>Slide 1</H3>
</script>
<script type="text/ng-template" id="pg2.html">
<H3>Slide 2</H3>
</script>
<script type="text/ng-template" id="pg3.html">
<H3>Slide 3</H3>
</script>
</div>
Here is my javascript:
function AppCtrl($scope, $location, viewSlideIndex) {
$scope.swipeLeft = function(){
$scope.slideDir = 'slide-left';
$scope.url = document.URL.substr(document.URL.lastIndexOf('/'));
if($scope.url == "/pg1"){
$scope.url = "/pg2";
}
else if ($scope.url == "/pg2"){
$scope.url = "/pg3";
}
else if ($scope.url == "/pg3"){
$scope.url = "/pg1";
}
$location.url($scope.url);
}
$scope.swipeRight = function(){
$scope.slideDir = 'slide-right';
$scope.url = document.URL.substr(document.URL.lastIndexOf('/'));
if($scope.url == "/pg1"){
$scope.url = "/pg3";
}
else if ($scope.url == "/pg2"){
$scope.url = "/pg1";
}
else if ($scope.url == "/pg3"){
$scope.url = "/pg2";
}
$location.url($scope.url);
}
};
angular.module('app', ['ngRoute', 'ngAnimate', 'ngTouch'])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/pg1', {
templateUrl: 'pg1.html',
controller: AppCtrl
});
$routeProvider.when('/pg2', {
templateUrl: 'pg2.html',
controller: AppCtrl
});
$routeProvider.when('/pg3', {
templateUrl: 'pg3.html',
controller: AppCtrl
});
$routeProvider.otherwise({
redirectTo: '/pg1'
});
$locationProvider.html5Mode(true);
}])
.service('viewSlideIndex', function () {
var viewIndex;
return {
getViewIndex: function () {
return viewIndex;
},
setViewIndex: function (val) {
viewIndex = val;
}
};
});
EDIT: To perform an actual swipe don't use the buttons but click-drag next to the text.
On your site, you have
$scope.slideDir = 'slide-right';
at the top of AppCtrl.
Remove this and it works. The code you have pasted here does not have that line and works as expected.