I have several regions with repeatable content which is generated on the server-side. I use knockout-js to dynamically hide/show regions within areas. My markup is like the following:
<div>
<input type="checkbox" data-bind="checked: a1" />
<div data-bind="visible: a1">region0</div>
</div>
<div>
<input type="checkbox" data-bind="checked: a2" />
<div data-bind="visible: a2">region1</div>
</div>
<script>
var viewModel = {
a1: ko.observable(false),
a2: ko.observable(false)
};
ko.applyBindings(viewModel);
</script>
Lets say I have 10 such regions. Is there a more convenient/better way to achieve the same?
Lets say, that I explicitly do not want to use foreach binding and generate markup on the client (for site to be accessible with disabled js).
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
P.S. I'm a JS-novice, so excuse me for simple questions :)
Is there any way to omit viewModel specification (part within script tags), since it feels to me that knockout could detect and auto-create fields for me?
Although I prefer Knockout personally, you might want to take a look at Angular. Angular does automatically create view model properties as this example shows.
Related
I have a problem when binding ng-models with ng-repeat in a input tag type checkbox.
I will first attach my code and then explain more in detail.
app/main.html:
<div ng-repeat="feature in features">
<input type="checkbox" ng-model="features[$index].name">{{features[$index].name}}
</div>
<br></br>
<div class="highlighter">
<span ng-class="{emo:Emotions}">Manually</span> <span ng-class="{feel:Feelings}">create</span> the <span ng-class="{emo:Emotions}">entire</span>
</div>
main.js
angular.module('webClientApp')
.controller('MainCtrl', function ($scope,$http) {
[...other variables...]
$scope.features = [{'name':'Emotions'},{'name':'Feelings'}];
[...other parts of code]
});
Let's also assume that in the main.css file there are references to the classes .emo' and.feel' respectively to highlight the target word when the user ticks the box relative to the feature.
Now, the application works correctly when I listed all the inputs one by one like the following:
<input type="checkbox" ng-model="Emotions">Emotions
<input type="checkbox" ng-model="Feelings">Feelings
but I wanted to wrap it into an ng-repeat and list the features in the controller scope, since the features I will considered will be more. When I try the code above when I tick on the box the name changes to `true'.
I have read a lot about how to bind models to an ng-repeat inside a input tag but none of the solutions apply to my case.
Can someone please help me?
I changed thigs up quite a bit from your original model but... I did get something to behave similar to what you are looking for.
HTML
<div ng-app="webClientApp">
<div ng-controller="MainCtrl">
<div ng-repeat="(feature,enabled) in features">
<input type="checkbox" ng-model="features[feature]">{{feature}}</input>
</div>
<div class="highlighter">
<span ng-class="{emo:features.Emotions}">Manually</span> <span ng-class="{feel:features.Feelings}">create</span> the <span ng-class="{emo:features.Emotions}">entire</span>
</div>
{{features}}<br>
{{features.Emotions}}<br>
{{features.Feelings}}
</div>
JS
var app = angular.module('webClientApp', []);
app.controller('MainCtrl', function($scope, $http) {
$scope.features = {Emotions: true, Feelings: true};
});
Here's the fiddle: http://jsfiddle.net/rodhartzell/8YrxQ/
Hope this helps.
(i should add this as a comment, but I don't have enough rep. yet)
There is an issue on github which concerns your issue: https://github.com/angular/angular.js/issues/1404 and the comment of caitp shows some workarounds: https://github.com/angular/angular.js/issues/1404#issuecomment-30859987
You could (also) define a new javascript object in your controller and map the elements to that.
In controller: $scope.awnsers = {};
In template: ng-model="awnsers[feature.name]"
I hope this helps
You must use ng-checked instead of ng-model.
Check out this jsfiddle
http://jsfiddle.net/fizerkhan/z5z9s/24/
ngModel and ngChecked are not meant to be used together.
ngChecked is expecting an expression, so by saying ng-checked="master". If the expression is truthy, then special attribute "checked" will be set on the element
You should be able to just use ngModel, tied to a boolean property on your model. If you want something else, then you either need to use ngTrueValue and ngFalseValue (which only support strings right now), or write your own directive.
I will start by explaining what we are trying to achieve.
We have just jumped on the Angular bandwagon and are building a prototype with it to see what it is capable of.
Currently there is a load of data (grades in this case) on the server and we display that data using Angular.
The following is the code we use:
<ul class="phones">
<li class="list-group-item" onclick="toggleExpandedView(this, true, 500)" ng-repeat="grade in grades | filter:query | orderBy:orderProp">
<span ng-show="grade.comment"><img src="../Content/images/comment.gif"/></span>
<a class="btn btn-primary editButton" style="float: right; position: absolute; right:10px;" ng-href="#/grades/{{grade.gradeId}}">Edit</a>
<div class="heading1"><a ng-href="{{grade.url}}" target="_blank">{{grade.gradeValue}}</a></div>
<div>Provided by {{grade.assessorFirstname}} {{grade.assessorLastname}} on {{grade.dateModifiedFormatted}} </div>
<div class="expandedGrade" onclick="childDivClick(event)" style="display: none" id="grade-{{grade.gradeId}}">
<label>Attachments</label>{{grade.attachmentCount}}
<br />
<span ng-hide="editing" ng-click="editing = true"><b>{{grade.comment || 'No comments yet'}}</b></span>
<form ng-show="editing" ng-submit="editing = false">
<input type="text" ng-model="grade.comment" placeholder="Comment" ng-required />
<br />
<input id="saveChanges" type="submit" class="btn btn-primary" ng-click="saveChanges(this, grade)" text="Save changes" />
</form>
</div>
</li>
</ul>
As you can see we have a parent ul and for each grade in grades we simply display a li and a div that is hidden and when you click on the li we use jQuery to animate and display the hidden div. In the child div we have a comments field which users can update and click save. On save the current object gets fired back to the server however we are concerned about the fact that Angular has to go through all 2000 grades until it finds the one we are updating (due to two way binding) and this means that everything will be really slow and we cannot afford that.
This is what we want to achieve:
1 We want to bind the data one way so that we can display the list of all grades on the screen and as soon as they are all displayed we want to somehow remove the bindings.
2. When users update the comments for a particular grade we then want to dynamically bind that particular grade so that Angular knows exactly which one it has to update without having to go through the whole collection of 2000+ grades.
I have find a tutorial however I am still unsure how to integrate that into my code.
I have also watched this video and I understand the concept behind it but again, I am struggling to write something that actually works ( we have just started using Angular so I am pretty much a newbie)
Could anyone out there point me in the right direction and provide me with some code samples that would solve the issue we are facing? Any advice and help would be greatly appreciated
You could always use a directive
The logic should go like this
use a service to hold your grades
inject the service into your directive
make a copy and just bind the 'read only view' in your directive
you could watch the service for changes and makes updates as needed
in your directive
as far a lazy loading / updating when needed - use a data service
and call the data service for an update whenever the trigger fires
if your trigger needs to come as a push from some other 'web service' consider a technology
like http://socket.io/
I can put together an example if you would like to see how the services and directives should interact
I'm building a filtering component for a search app and it's my first dip into AngularJS.
Here's a Plunker or what I've got so far, it works how I want it to:
http://plnkr.co/I6ewaU
There are two things which concern me with this implementation:
I have to pass filters as an attribute of the filter directive to get access to filters on the MainController, surely there must be a better way to do this? I want to modify the filters object without having to pass it as an attribute.
Is it correct to define addFilter in the filtergroup directive, or should this be defined on MainController?
I have been searching on Google for a while, but can't seem to find an alternative and would appreciate any help with this.
Cheers.
AngularJS way is to show what it does in html.
Html has to explain the functionality by not hiding it to JS.
For example, how do I know this update filters unless I read your JS? Thus, try not to make any unless it is necessary. If you make any directive, try to expose the functionality to html.
<filtergroup type="filter-name" filter-store="filters">
And there are lots of good directives in Angularjs. I always use them first.
This is how I would do it, http://plnkr.co/edit/okCp5FZJo1ZR9962uaHT?p=preview
<div class="sidebar">
<p>Filters: {{ filters }}</p>
filter1 <input type="checkbox" ng-model="filters[1]"/><br/>
filter2 <input type="checkbox" ng-model="filters[2]"/><br/>
filter3 <input type="checkbox" ng-model="filters[3]"/><br/>
filter4 <input type="checkbox" ng-model="filters[4]"/><br/>
filter5 <input type="checkbox" ng-model="filters[5]"/><br/>
</div>
"less code, less manitenance"
Alright, newbie Knockout question here.
In this example: http://learn.knockoutjs.com/WebmailExampleStandalone.html#Inbox
How does the mail detail view replace the folder list view?
That is, what feature causes the divs to be toggled? In inspecting the dom, I see that what happens is the div's are actually rendered empty when not displayed.
Can someone enlighten me? I know this is rather basic, but its the last piece I need to click into place for my understanding.
Just to be 100% clear: when you click on a row in the folder list, what causes the folder view to be emptied and the mail detail to display? Is it the with binding? (That doesn't seem right.)
You are on the right track with the with binding: in this example the views are changed using the with binding relaying on this feature of the binding:
The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not
So in the viewmodel code you will see something like this:
this.get('#:folder', function () {
self.chosenFolderId(this.params.folder);
self.chosenMailData(null);
$.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
});
this.get('#:folder/:mailId', function () {
self.chosenFolderId(this.params.folder);
self.chosenFolderData(null);
$.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
});
So the functions which are "chaining" the view nulls out one of the properties while filling in the other which toggles the views defined as:
<!-- Chosen mail -->
<div class="viewMail" data-bind="with: chosenMailData">
...
<div/>
<!-- Mails grid -->
<table class="mails" data-bind="with: chosenFolderData">
</table>
This is not the nicest solution but don't forget that Knockout is a Databind/MVVM library and not a full blown SPA framework so it does not have concepts for layouting and higher level view composition.
However this could be made nicer with using the template binding:
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
And turning the views into templates:
<script type="text/html" id="ChosenMail">
<div class="viewMail">
...
<div/>
</script>
<script type="text/html" id="MailsGrid">
<table class="mails">
...
</table>
</script>
And in the routing only set the activeView property and lookup the corresponding template name for it:
this.get('#:folder', function () {
$.get("/mail", { folder: this.params.folder }, function(data) {
self.activeView(data);
self.templateName('ChosenMail');
});;
});
this.get('#:folder/:mailId', function () {
$.get("/mail", { mailId: this.params.mailId }, function(data) {
self.activeView(data);
self.templateName('MailsGrid');
});
});
But because this is quite much manual and error prone work I would use something like Durandal.js which is a real SPA framework and it was designed for this kind scenarios.
That is just a Demo on a light weight SPA scenario, with binding is just a inline template binding. Not very useful for a dynamic SPA. Like Nemesv suggests use the template binding.
The problem with the template binding is that its very verbose to use, I have addressed this in my Binding convention library (One of many features)
Instead of doing
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
You do
<div id="mainView" data-name="activeView">
</div>
My library will do the rest, check out the wiki on templates here
https://github.com/AndersMalmgren/Knockout.BindingConventions/wiki/Template-convention
And a little fiddle
http://jsfiddle.net/xJL7u/11/
Subj.
For example...
Check all
<p>
<input type="checkbox" name="category[1]" tag="category_checkbox" /> Category 1
<input type="checkbox" name="category[2]" tag="category_checkbox" /> Category 2
</p>
PS: What advantages and disadvantages of this method? Please explain why.
By the way, if you are using only id,classes for selecting items. please describe what names you are using for it?
Adding additional attributes to the markup feels dirty some how, so I've settled for classes and id prefixes.
Classes like: "click-capture" for setting up hijaxing in a nesting friendly way a la .live() and $(this).closest(".click-capture").laod(...)
Parenting in a 2 layer tree via #afd98f for parent and .afd98f for children.