In my application I'm returning the view below which uses this TestViewModel.
public class TestViewModel
{
public List<string> Tests { get; set; }
}
View:
#model AdminSite.Models.TestsViewModel
#{
ViewBag.Title = "Tests";
}
<hgroup class="title">
<h1>#ViewBag.Title</h1>
</hgroup>
<!doctype html>
<html>
<head>
<script src="~/Scripts/Angular/angular.min.js"></script>
<script src="~/Scripts/Angular/Tests.js"></script>
</head>
<body>
<div ng-app="testsTable">
<div ng-controller="TableController">
<table my-table options="options"></table>
</div>
</div>
</body>
</html>
As you can see I'm using AngularJS to create a DataTable, what I would like though is that instead of the table data "aaData" being hardcoded I want it to be from TestViewModel model.
var app = angular.module('testsTable', []);
app.directive('myTable', function () {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
var dataTable = element.dataTable(scope.options);
},
scope: {
options: "="
}
};
});
app.controller('TableController', ['$scope', function ($scope) {
$scope.options = {
aoColumns: [
{
"sTitle": "Col 1",
},
{
"sTitle": "Col 2"
},
{
"sTitle": "Col 3"
}
],
aoColumnDefs: [{
"bSortable": true,
}],
bJQueryUI: true,
bDestroy: true,
aaData: [
["A", "B", "C"],
["A", "B", "C"],
]
};
}]);
I think I may have to create another directive to bind the model e.g
<table my-table options="options" model-data="#Model.Tests"></table>
but I'm not really sure how Angular directives work exactly, how I would write said directive & how it binds it to the scope
Your question is answered here
You could directly inject the values into JavaScript:
//View.cshtml
<script type="text/javascript">
var arrayOfArrays = JSON.parse('#Html.Raw(Model.Addresses)');
</script>
See JSON.parse, Html.Raw
Alternatively you can get the values via Ajax:
public ActionResult GetValues()
{
// logic
// Edit you don't need to serialize it just return the object
return Json(new { Addresses: lAddressGeocodeModel });
}
<script type="text/javascript">
$(function() {
$.ajax({
type: 'POST',
url: '#Url.Action("GetValues")',
success: function(result) {
// do something with result
}
});
});
</script>
See jQuery.ajax
So...Setting aaData using {{options.aaData=JSON.parse('#Html.Raw(#Model.Tests)');""}} was working but I had nothing monitoring the change meaning the table wasn't being redrawn with the new information.
to track changes I added a watch in my directive, this monitors aaData for changes then updates the table as necessary.
scope.$watch('options.aaData', handleUpdate, true);
function handleUpdate(newData) {
var data = newData || null;
if (data) {
dataTable.fnClearTable();
dataTable.fnAddData(newData);
}
}
my real solution looks a little different now as I am using dataTables ajax call to pull down my json data then setting the dataSrc with the result. Using the in-built ajax calls means I can remove the directive watch as datatables handles this for me.
One final note, if you've found yourself here while figuring out how to mix angularJS & dataTables then this fiddle has helped me a lot.
http://jsfiddle.net/TNy3w/2/
Since you're working with angular, take advantage of angular configuration:
<script>
(function () {
angular.module('app').value('mydata', {
propOne: '#Url.Action("Index", "Dirty", null)',
propTwo: angular.fromJson('#Html.Raw(Model.Addresses)')
// more properties here
});
})();
</script>
Then you can inject and use 'mydata' anywhere within your angular application
EDIT:
You can create an extension method that serializes your model to JSON, then Html.Raw won't give you an error, as long as the parameter you provide is not null.
public static class ObjectExtensions
{
public static string SerializeObject(this object obj) {
return JsonConvert.SerializeObject(
obj,
new JsonSerializerSettings {
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
}
Then do:
#using [static extension class namespace here];
...
angular.fromJson('#Html.Raw(Model.Addresses.SerializeObject())')
Related
I'm loading a Partial view which contains AngularJS code, using the code below:
http.post("/Admin/LoadPartial", {
path: "~/Views/Admin/Urchin/Modals/_ChangeHero.cshtml",
model: self.newID
}).then(function (res) {
//res.data is the .cshtml
var element = angular.element(res.data);
var modal = $compile(element)(scope);
self.newSlides.push({
"ID": self.newID,
"Data": self.newData,
"Modal": modal.html()
});
scope.$emit("ngRepeatFinished");
Notify.Show("Saul goodman", "notice");});
This is how I render the partial:
<div ng-repeat="item in h.newSlides"
ng-bind-html="item.Modal | to_trusted" id="Hey-{{item.ID}}"></div>
And the filter:
.filter('to_trusted', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
The problem:
The rendered partial loads as HTML, but it contains code like this:
<button id="bgProg-#Model" class="progress-button" ng-click="h.EditBG()">
where h is the controller that loaded the .cshtml, and no click event is bound to the button.
Any ideas as to where I'm doing things wrong are greatly appreciated.
Progress
Thank you #user1620220 for the response.
I added this right after Notify.Show(.. :
timeout(function () {
var what = document.getElementById("Hey-" + self.newID);
var element = angular.element(what);
var modal = $compile(element)(scope);
what.innerHTML = content;}, 0, false);
and still no bindings are happening.
You are using $compile to generate a compiled node, but then you are calling html() to convert the node back to a string. ng-bind-html then converts the string into an uncompiled node and adds it to the DOM.
Instead, just pass res.data to ng-bind-html, allow the digest loop to run, then compile the in-situ DOM node.
Edit: After reading your edit, it occurred to me you need to use the cloneAttachFn returned by $compile. Here is my new proposed solution:
HTML:
<div ng-repeat="item in h.newSlides">
<div id="Hey-{{item.ID}}"><!--place holder--></div>
</div>
JS:
var linkFn = $compile(res.data);
timeout(function () {
self.newSlides.forEach((slide) => {
var what = document.getElementById("Hey-" + slide.ID);
linkFn(scope,(clone) => {
what.parentNode.replaceChild(clone, what);
});
});
}, 0, false);
We have this:
<td ... ng-click="someFunction(param)" ...></td>
The function is defined in one of the controllers in controllers.js:
$scope.someFunction= function (param) { ... }
and the param is expected to be translated string. I tried different approaches, e.g.:
<td ... ng-click="someFunction('PARAM_TRANSLATION_KEY' | translate)" ...></td>
or:
<td ... ng-click="someFunction({{'PARAM_TRANSLATION_KEY' | translate}})" ...></td>
and also others, but nothing seems to be working and I couldn't find this in the angular-translate documentation and/or I-net.
Any ideas?
angular translate has a translation service that you can use inside your js code ,
https://angular-translate.github.io/docs/#/api/pascalprecht.translate.$translate
simply pass the function as
ng-click="someFunction('PARAM_TRANSLATION_KEY')
and use the service inside the function like so:
function(param) { $translate(param).then(translation) { ... } }
UPDATE : if you cant change the code then you can pass the param like so
Add the service to the scope so you can access it
Use the service to translate and call your function after callback
Example:
var translations = {
HEADLINE: 'What an awesome module!',
PARAGRAPH: 'Srsly!',
NAMESPACE: {
PARAGRAPH: 'And it comes with awesome features!'
}
};
var app = angular.module('myApp', ['pascalprecht.translate']);
app.config(['$translateProvider',
function($translateProvider) {
// add translation table
$translateProvider
.translations('en', translations)
.preferredLanguage('en');
}
]);
app.controller('myController', function($scope, $translate) {
$scope.translationServiceCallback = function(param, cb) {
$translate(param).then(function(translation) {
cb(translation);
});
}
$scope.log = function(param) {
console.log(param)
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-translate/2.11.1/angular-translate.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<div>
<button ng-click="translationServiceCallback('HEADLINE',log)">Translate</button>
<button ng-click="log('HEADLINE')">WYSIWYG</button>
</div>
</div>
</div>
I follow the example from this book https://leanpub.com/marionette-gentle-introduction. My problem is that the view does not rerender when i change the model by clicking on the button. As the answer from this question , i don't need to do anything because Backbone/MarionetteJS smart enough to change the view.
Here is the code
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo marionettejs</title>
<script src="./vendors/jquery/dist/jquery.js" type="text/javascript"></script>
<script src="./vendors/underscore/underscore.js" type="text/javascript"></script>
<script src="./vendors/backbone/backbone.js" type="text/javascript"></script>
<script src="./vendors/backbone.marionette/lib/backbone.marionette.js" type="text/javascript"></script>
</head>
<body>
<div id="main-region" class="container">
<p>Here is static content in the web page. You'll notice that it gets
replaced by our app as soon as we start it.</p>
</div>
<script type="text/template" id="contact-template">
<p><%- firstName %> <%- lastName %> : <%- time %> </p> <br />
<button>Change model</button>
</script>
<script type="text/javascript">
var ContactManager = new Marionette.Application();
ContactManager.Contact = Backbone.Model.extend({});
ContactManager.ContactView = Marionette.ItemView.extend({
template: "#contact-template",
initialize: function () {
this.currentMeterId = null;
},
events: {
"click button": "changeModel"
},
modelEvents: {
"change": "modelChanged"
},
changeModel: function() {
this.model.set("time", (new Date()).toString());
},
modelChanged: function() {
console.log("Model changed : " + this.model.get('time'));
},
//EDIT
onRender: function() {
//Create jsTree here.
}
});
ContactManager.on("before:start", function () {
var RegionContainer = Marionette.LayoutView.extend({
el: "#app-container",
regions: {
main: "#main-region"
}
});
ContactManager.regions = new RegionContainer();
});
ContactManager.on("start", function () {
var alice = new ContactManager.Contact({
firstName: "Alice",
lastName: "Arten",
time: "#"
});
var aliceView = new ContactManager.ContactView({
model: alice
});
ContactManager.regions.main.show(aliceView);
});
ContactManager.start();
</script>
</body>
</html>
#Edit
This code is just sample. In my real app, I have an ajax task that changes DOMs in the view. This ajax task creates a tree (jsTree) in onRender event. If i use modelEvents: {"change": "render"}, my jsTree will be reload and lost its state. So I want only update the model values in the view, others DOMs is retain.
The accepted answer to the question you pointed points to another question which has the following:
modelEvents: {
'change': "modelChanged"
},
modelChanged: function() {
console.log(this.model);
this.render();
}
And the most upvoted answer suggests the same:
modelEvents: {
'change': 'fieldsChanged'
},
fieldsChanged: function() {
this.render();
}
a comment to the most upvoted answer suggests
just {'change': 'render'} does the trick too
Which means you can do
modelEvents: {
'change': 'render'
}
So somehow you need to tell marionette invoke render on model changes.
I don't think backbone and marionette couple is smart enough to know whether you need to render view on model changes or you don't want to unless you tell them ;)
I have the following Angular controllers:
function TagListController($scope) {
$scope.model = {
tags: ['tag 1', 'tag 2', 'tag 3' ],
template: 'list'
}
$scope.showTemplate = function (template) {
$scope.model.template = template;
};
}
function TagEditController($scope) {
$scope.tagToEdit = $scope.$parent.model ???
}
And I have the following HTML to show a list of tags and to edit a tag:
<ul ng-if="model.template == 'list'" ng-controller="TagListController">
<li data-ng-repeat="tag in model.tags">
<span data-ng-bind="tag.name"></span></br>
<a href="" data-ng-click="show('edit')"</a>
</li>
</ul>
<script type="text/ng-template" id="edit">
<div ng-controller="TagEditController">
Edit template
</div>
</script>
<div ng-if="model.template == 'edit'" ng-include="'edit'"></div>
Using model.template I am able to show the Edit template.
But how can I, in EditTagController, use the tag which Edit link was clicked?
I know it will be one of the items in $scope.$parent.model.tags.
I just don't know how to select it and the best way to select it.
This would be a good place to use a factory or service.
See this jsBin
You want to store common data inside of your factory instead of dealing with $parent or $rootScope.
You could create a factory like this:
function tagService() {
var observerCallbacks = [];
var currentTag;
function getCurrentTag() {
return currentTag;
}
function setCurrentTag(tag) {
currentTag = tag;
notifyObservers();
}
function notifyObservers() {
angular.forEach(observerCallbacks, function(callback) {
callback();
});
}
function registerObserverCallback(callback) {
this.observerCallbacks.push(callback);
notifyObservers();
}
return {
getCurrentTag: getCurrentTag,
setCurrentTag: setCurrentTag,
registerObserverCallback: registerObserverCallback,
notifyObservers: notifyObservers
};
}
Here, I'm also using the observer pattern to notify anybody who is listening for changes any time setCurrentTag is called. That way, if you change tags, both controllers know.
Your TagListController might look like:
function TagListController($scope, tagService) {
$scope.model = {
tags: ['tag 1', 'tag 2', 'tag 3' ],
template: 'list'
};
$scope.editTag = function(tag) {
tagService.setCurrentTag(tag);
$scope.model.template = 'edit';
};
}
And your TagEditController like:
function TagEditController($scope, tagService) {
tagService.registerObserverCallback(function() {
$scope.tagToEdit = tagService.getCurrentTag();
});
}
How about using Angular routing? You could set up a route like "yourapp/tags/{tag}" to your EditTagController and that way you will get the current tag as a parameter.
Something like this
Trying to simulate a hotel cart.
Newbie here
Questions
1. How to add an item to orders when clicked on corresponding Add button
2. Is it correct to use a factory for serving both menuitems for menu directive and orderItems for cart directive
3. On click of add button, where should the to be called add function be written, in the factory or in the directive's controller
4. Is there any way to better this code and its logic?
For those who wish to see the plunkr demo can view the same here
HTML snippet
<menu></menu>
JS snippet
angular.module('myApp',[])
.factory('menuItems',function(){
return {
list:function(){
var items = [{'name':'kabab'},
{'name':'chicken'},
{'name':'egg'},
{'name':'noodles'}];
return items
}
};
})
.factory('cartItems',function(){
var orders = [];
return orders;
})
.directive('menu',function(){
return {
restrict:'E',
template:"<ul><li ng-repeat='item in menuItems'>"+
'{{item.name}}' +
"</li></ul>",
scope:{},
controllerAs:'menuCtrl',
controller:function($scope, menuItems){
console.log("I am in menuDirective and outputting menuItems")
console.log(menuItems);
$scope.menuItems = menuItems.list();
},
link:function(){
}
}
})
.directive('cart',function(){
return{
restrict:'E',
template:"<ul><li ng-repeat='order in cartItems'>"+
'{{order.name}}' +
"</li></ul>",
scope:{},
controller:function($scope,cartItems){
$scope.cartItems = cartItems.list();
},
link:function(){
}}
})
Plunker Demo
I think this is a fine way to do it. Since you need to access the same dataset from multiple directives, it makes sense to me to put the methods and data into a factory.
HTML:
<body ng-controller="MainCtrl">
<menu></menu>
<br />Cart:<br />
<cart></cart>
</body>
JS:
angular.module('plunker',[])
.controller('MainCtrl', function() {
})
.factory('menuItems',function(){
return {
list:function(){
var items = [{'name':'kabab'},
{'name':'chicken'},
{'name':'egg'},
{'name':'noodles'}];
return items
}
};
})
.factory('cartItems',function(){
return {
orders: [],
add: function(item) {
this.orders.push(item)
}
}
//return this.orders;
})
.directive('menu',function(cartItems){
return {
restrict:'E',
template:"<ul><li ng-repeat='item in menuItems'>"+
'{{item.name}}' +
"<button ng-click='addItem(item.name)'>Add</button></li></ul>",
scope:{},
controllerAs:'menuCtrl',
controller:function($scope, menuItems){
console.log("I am in menuDirective and outputting menuItems")
console.log(menuItems);
$scope.menuItems = menuItems.list();
$scope.addItem = function(item) {
cartItems.add(item);
console.log(cartItems.orders)
}
},
link:function(){
}
}
})
.directive('cart',function(){
return{
restrict:'E',
template:"<ul><li ng-repeat='order in cartItems track by $index'>"+
'{{order}}' +
"</li></ul>",
scope:{},
controller:function($scope,cartItems){
$scope.cartItems = cartItems.orders;
},
link:function(){
}}
})
Any methods relating to the orders array should be put in the factory. Your factory should encapsulate all logic related to that particular 'thing'. Keeping the logic out of your directives gives you good abstraction, so if you need to change something, you know where all the logic is kept, rather than spread out in a bunch of directives. So if you want to add an 'empty cart' method, or a 'delete from cart' method, I would put those into the factory, operate directly on the orders array, and return the updated array to whatever is calling the methods.