Ember 2 : dynamic CSS class with parameters - javascript

I'm currently on an Ember 2.4 project and I'm integrating a form divided in "steps", like this :
<form>
<div id="step_1">
<!-- blabla -->
</div>
<div id="step_2">
<!-- blabla -->
</div>
<div id="step_3">
<!-- blabla -->
</div>
</form>
I would like to display only one item in the same time.
I would like to manage it dynamically because this form can evolve in lots of steps...
The idea I had was to use a currentStep var in the controller, and a test that check if the parameter passed was the currentStep.
But in Ember and Handlebars, seems impossible to use methods giving arguments to make a test, something like that for example :
<div id="step_1" class={{isCurrentStep(1)}}>
<!-- blabla -->
</div>
<div id="step_2" class={{isCurrentStep(2)}}>
<!-- blabla -->
</div>
<div id="step_3" class={{isCurrentStep(3)}}>
<!-- blabla -->
</div>
Or, at least, something like that :
<div id="step_1" class={{isCurrentStep(1):visible:invisible}}>
<!-- blabla -->
</div>
<div id="step_2" class={{isCurrentStep(2):visible:invisible}}>
<!-- blabla -->
</div>
<div id="step_3" class={{isCurrentStep(3):visible:invisible}}>
<!-- blabla -->
</div>
The idea behind is to manage a function like :
isCurrentStep: function(id){
return this.get('currentStep') == id ? "visible" : "hidden";
}
Or even simply returning true / false and manage the class toggle in the HBS file.
Do you have a "not complicated" solution, or even a better idea to manage my problem, to do that ? I don't wanna create as much functions as there are steps, like :
isCurrentStep1: function(){
return this.get('currentStep') == 1;
}
isCurrentStep2: function(){
return this.get('currentStep') == 2;
}
isCurrentStep3: function(){
return this.get('currentStep') == 3;
}

I would recommend to write a component for each step and use the {{component}} helper to show your current component. Or maybe even use your router.
But what you want is also easy. You need a property currStep on your controller/component, and a simple is-equal helper:
//helpers/is-equal
import Ember from 'ember';
export function boolOr([left, right]/*, hash*/) {
return return left == right;
}
export default Ember.Helper.helper(boolOr);
then you can do this in your template:
<div class="{{if (is-equal currStep '1') 'visible' 'invisible'}}">...</div>

Related

How to manage html logic in a separate file

i have a piece of html code that in charge of presenting a list based on certain conditions:
<!-- Show list only if there are more than 5 results -->
<div list.numberOfResults > 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
<!-- Show list only if there are less than 10 results -->
<div list.numberOfResults < 10">
<b>Name: </b>{{list.name}} <b>ID: </b>{{list.id}}
<b>Country: </b>{{list.country}}
</div>
Now, I also have some optional parameter (list.country) so I need to check if its not empty before as well.
I believe there is a way to take this logic outside of this html file and make a file that is responsable of the logic and the html will present the data accordingly, can someone please share a simple example of how this can be done based on my code?
thanks!!
Since There are two component you can keep them in a separate file like name
component.html
Then you can import in index.html like
<link rel="import" href="component.html" >
Or you can just grab specific portion from the file like
...
<script>
var link = document.querySelector('link[rel="import"]');
var content = link.import;
// Grab DOM from component.html's document.
var el = content.querySelector('.component');
document.body.appendChild(el.cloneNode(true));
</script>
</body>
DEMO :https://plnkr.co/edit/6atoS1WOK5RzKSmNeR8n?p=preview
You can use ngSwitch when you want to show HTML pieces conditionally,
#Component({
selector: 'my-app',
template: `
<div [ngSwitch]="list.numberOfResults>10"> // here
<div *ngSwitchCase ="true"> // here
<b> Template1 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div *ngSwitchCase ="false"> // here
<b> Template2 </b><br>
<b>Name: </b>{{list.name}} <br>
<b>ID: </b>{{list.id}} <br>
<b>Country: </b>{{list.country}}
</div>
<div>
`,
})
export class App {
list={numberOfResults:2,name:'myList',id:1,country:'USA'};
}

I have a 100 button, click each button to display its corresponding bomb box, With javascript and angular

a button corresponding to a prompt box,each box is different shells;Although implements the desired function, but my code is too complicated, and that there is no simple way. how can I do? This is my code
<--html button-->
button1
button2
...
button100
<--html pop box-->
<div class="note1" style="display:none;">
<img class="title-css" src="note1.png">
<p class="one">note1</p>
</div>
...
<div class="note100" style="display:none;">
<img class="title-css" src="note100.png">
<p class="one">note100</p>
</div>
<--angular js-->
$scope.showRulePop = function(index) {
for(var i=1;i<=8;i++) {
$('.note'+i).hide();
}
$('.note'+index).show();
};
Well first of all, don't use jQuery, unless your in the directive level of angular jQuery have nothing to do there.
First let's get rid of the links part using a simple ng-repeat :
<--html button-->
<div ng-repeat="button in buttons">
{{button.label[i]}}
</div>
// JS in the controller
$scope.buttons = [{
label:'button1'
},{label:'button2'}];
As you can see i declare in the javascript all your buttons and i just loop over it.
Now the "bombox" or whatever it is let's make it a simple template :
<div class="{{currentnote.class}}" ng-if="currentNote">
<img class="title-css" src="{{currentNote.img}}">
<p class="one">{{currentNote.content}}</p>
</div>
// and use ng-repeat for the eight first when there is no button selected
<!-- show 1 to 8 if note current note selected -->
<div ng-repeat="button in buttons1To8" ng-if="!currentNote">
<div class="{{button.note.class}}">
<img class="title-css" src="{{button.note.img}}">
<p class="one">{{button.note.content}}</p>
</div>
</div>
// JS
$scope.buttons = [{
label:'button1'
note:{class:'note1', img:'note1.png', content:'note1'//assuming no HTML or you' ll need something more
}},{label:'button2', note:{...}}, ...];
$scope.showRulePop = function(index){
$scope.currentNote = $scope.buttons[index].note;
}
$scope.buttons1To8 = $scope.buttons.slice(0, 8);//0 to 7 in fact
That's all, no need of jQuery.

Knockout view not removing item from view after delete

I have this code that successfully deletes an exam from a list of exams displayed on a page, but the page still shows the deleted exam. You have to manually refresh the page for the view to update. We are using a simlar pattern on other pages and it's working correctly. I don't understand why it doesn't work on this page.
// Used to handle the click event for Delete
remove = (exam: Models.Exam) => {
$("#loadingScreen").css("display", "block");
var examService = new Services.ExamService();
examService.remove(exam.examId()).then(() => {
examService.getByFid().then((examinations: Array<Models.Exam>) => {
this.exams(examinations);
this.template("mainTemplate");
});
}).fail((error: any) => {
// Add this error to errors
this.errors([error]);
window.scrollTo(0, 0);
}).fin(() => {
$("#loadingScreen").css("display", "none");
});
}
Here's the UI code that displays the list of exams
<div class="section module">
<!-- ko if: exams().length > 0 -->
<!-- ko foreach: exams.sort(function(a,b){return a.mostRecentDateTaken() > b.mostRecentDateTaken() ? 1:-1}) -->
<div class="addremove_section bubbled">
<a class="button open_review" data-bind="click: $root.edit">Edit</a>
<a class="button open_review" data-bind="click: $root.remove">Delete</a>
<div class="titleblock">
<h4 data-bind="text: 'Exam Name: ' + examTypeLookup().examTypeName()"></h4>
<div data-bind="if:examEntityLookup()!=null">
<div data-bind=" text: 'Reporting Entity: ' + examEntityLookup().description()"></div>
</div>
<div data-bind="text: 'Most recent date taken: ' + $root.formatDate(mostRecentDateTaken())"></div>
<div data-bind="text: 'Number of attempts: ' + numberOfAttempts()"></div>
<div data-bind="text: 'Pass/Fail Status: ' + $root.PassFailEnum(passFailId())"></div>
</div>
<div class="clearfix"></div>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- ko if: exams().length == 0 -->
<div class="addremove_section bubbled">
<div class="titleblock">
<div>No Exams Have Been Entered.</div>
</div>
</div>
<!-- /ko -->
</div>
EDIT: I discovered that if I remove the sort from this line in the view
<!-- ko foreach: exams.sort(function(a,b){return a.mostRecentDateTaken() > b.mostRecentDateTaken() ? 1:-1}) -->
to
<!-- ko foreach: exams -->
it works! The only problem is that I need the data sorted.
I removed the sorting from the view and did the sorting in the service. Not really sure why I can't sort in the view. I assume it's a knockout.js bug.
<!-- ko foreach: exams -->
[HttpGet]
[Route("api/exam")]
public IEnumerable<TDto> GetApplicantExams()
{
var dtos = GetCollection(() => _examService.GetApplicantExams(UserContext.Fid).OrderBy(e => e.DateTaken));
return dtos.ForEach(t => AddItems(t));
}
It's not a bug in Knockout. Since the sort invocation (as you're doing it) is not under the control of a computed/dependent observable, there's nothing to trigger it to resort. You've basically broken the connection between the UI (or more technically, the bindingHandler) and the ko.observable which KO uses for tracking changes.
I've run into this many times where I work, and the general pattern I use is something like this:
var viewmodel = {
listOfObjects:ko.observableArray(),
deleteFromList:deleteFromList,
addToList:addToList,
}
//in your HTML, you'll do a foreach: listOfSortedObjects (instead of listOfObjects)
viewmodel.listOfSortedObjects = ko.computed(function(){
//Since this is *inside* the change tracking ecosystem, this sort will be called as you would expect
return viewmodel.listOfObjects().sort(yourSortingFunction);
});
//Since you cannot edit a (normal) computed, you need to do your work on the original array as below.
function deleteFromList(item){
viewmodel.listOfObjects.remove(item);//Changing this array will trigger the sorted computed to update, which will update your UI
}
function addToList(item){
viewmodel.listOfObjects.push(item);
}

How can a data-bind to an element within a Kendo-Knockout listview?

I have a rather sophisticated template for Kendo ListView using knockout-kendo.js bindings. It displays beautifully. My problem is that I need to use the visible and click bindings in parts of the template, but I can't get them to work. Below is a simplified version of my template. Basically, deleteButtonVisible determines whether the close button can be seen, and removeComp removes the item from the array.
<div class='template'>
<div >
<div style='display:inline-block' data-bind='visible: deleteButtonVisible, event: {click: $parent.removeComp}'>
<img src='../../../Img/dialog_close.png'></img>
</div>
<div class='embolden'>#= type#</div><div class='label1'> #= marketArea# </div>
<div class='label2'> #= address# </div>
<!-- more of the same -->
</div>
The view model:
function CompViewModel() {
var self = this;
self.compData = ko.observableArray().subscribeTo("compData");
self.template = kendo.template(//template in here);
self.removeComp = function (comp) {
//do something here
}
}
html:
<div class="row" >
<div class="col-md-12 centerouter" id="compDiv" >
<div class="centerinner" id="compListView" data-bind="kendoListView: {data: compData, template: template}"></div>
</div>
</div>
finally, sample data:
{
type: "Comparable",
marketArea: "",
address: "2327 Bristol St",
deleteButtonVisible: true
},
Take in count that the deleteButtonVisible must be a property on the viewModel linked to the view.You are not doing that right now. The click element can v¡be access from the outer scope of the binding and remove the $parent.He take the method from the viewmodel. Take in count that every thing that you take on the vie must be present on the view model for a easy access.

Render container conditionally

Is it possible to render container for a template based on condition with knockout.js?
This does not work, but shows what i want to do:
<div data-bind="foreach: items">
<!-- ko if: $data.startContainer -->
<div class="container">
<!-- ko -->
<div data-bind="html: $data.contentElement"></div>
<!-- ko if: $data.endContainer -->
</div>
<!-- ko -->
</div>
Found a thread on knockout.js github site that indicates this as not possible with the native templating model:
https://github.com/SteveSanderson/knockout/issues/307
Apparently, the closing comment is understand as internal to the not closed div tag.
My hopes were on the dynamic templates, but failed also like shown in the fiddle.
http://jsfiddle.net/XbdGs/3/
<script type="text/html" id="withContainer">
<div class="container">
<!-- ko template: 'withoutContainer' -->
<!-- /ko -->
</div>
</script>
From that i conclude you can try the 3 foreachs solution, use Posthuma suggestion or fallback to another templating engine like jquery.tmpl or Underscore as mentioned on knockout documentation.
http://knockoutjs.com/documentation/template-binding.html
You can do this through a custom binding.
Update:
If you want to open a div and close from another item, the custom binding would look like this:
ko.bindingHandlers.myCustomBinding = {
update: function(element, valueAccessor, allBindings, data, context){
var value = valueAccessor();
var items = ko.utils.unwrapObservable(value);
var currentElement = element;
ko.utils.arrayForEach(items, function(item){
if(item.startContainer){
var container = document.createElement('div');
$(container).append(item.displayContent);
$(container).addClass("container");
currentElement = container;
}
else if(item.endContainer){
$(currentElement).append(item.displayContent);
$(element).append(currentElement);
currentElement = element;
}
else{
$(currentElement).append(item.displayContent);
}
});
}
};
HTML:
<div data-bind='myCustomBinding: items'></div>
There are probably better ways to write this code and possibly use knockouts built-in bindings, but this should be enough to get you started.
http://jsfiddle.net/posthuma/f5wG4/2

Categories

Resources