I am working with the following objects/structure: Course, SubCategory, SubUniversity, Category, SubCategory, CourseSchedule.
A course can have one and only one subcategory, but can be a part of many subuniversities (hence the CourseSchedule object with one Course and one SubUniversity).
Each SubCategory has one parent Category; each SubUniversity has one parent University.
I have a courseadd view and a courseedit view. Once the Course object is created with the courseadd view, SubUniversities (via CourseSchedules) can be added on the courseedit view.
When I try to add SubUniversites, the first appears twice.
When I add subsequent SubUniversites, they appear correctly with the first still being duplicated.
Here is the View Code
<section id="course-edit" class="view">
<h3 class="page-title" data-bind="text: title"></h3>
<div class="button-bar">
<button class="btn btn-info"
data-bind="click: goBack"><i class="icon-hand-left"></i></button>
<button class="btn btn-info"
data-bind="click: cancel, enable: canSave"><i class="icon-undo"></i> Cancel</button>
<button class="btn btn-info"
data-bind="click: save, enable: canSave"><i class="icon-save"></i> Save</button>
<button class="btn btn-danger"
data-bind="click: deleteCourse, disable: hasChanges">
<i class="icon-trash"></i> Delete
</button>
<i class="icon-asterisk" data-bind="visible: hasChanges"></i>
</div>
<div data-bind="with: course">
<div>
<label for="courseName">Name</label>
<input id="courseName" data-bind="value: courseName" placeholder="Course Name" />
</div>
<div>
<label for="category">Category</label>
<select id="category" data-bind="options: $parent.subcategories, optionsText: 'subCategoryName', value: subCategory"></select>
</div>
<div>
<label for="courseMaterialURL">Material URL</label>
<input id="courseMaterialURL" data-bind="value: courseMaterialURL" placeholder="http://" />
</div>
<div>
<label for="courseImageURL">Image URL</label>
<input id="courseImageURL" data-bind="value: courseImageURL" placeholder="http://" />
</div>
<div>
<label for="courseDescription">Description</label>
<textarea id="courseDescription" data-bind="value: courseDescription" placeholder="Course Description" rows="4"></textarea>
</div>
<div style="width:600px">
<div style="float:right">
<label for="courseUniversity"> </label>
<section id="courseScheduleNode" class="view-list" data-bind="foreach: courseSchedules" >
<article>
<div>
<span style="margin-right: 10px" data-bind="text: subUniversity().subUniversityName"></span>
<button class="btn btn-danger" data-bind="click: $root.removeSubUniversity" style="float:right"><i class="icon-remove"></i></button>
</div>
<br />
</article>
</section>
</div>
<div>
<label for="courseUniversity">Add University</label>
<select id="courseUniversity" data-bind="options: $parent.subuniversities, optionsText: 'subUniversityName', value: selectedSubUniversity, optionsCaption: ' '"></select>
<button class="btn btn-success" data-bind="click: $parent.addSubUniversity"><i class="icon-ok"></i></button>
</div>
</div>
</div>
</section>
This part of the viewmodel is the code for the add and remove onClick functions.
var addSubUniversity = function (selectedCourse) {
if (selectedCourse) {
var cs = datacontext.createCourseSchedule();
cs.courseId(selectedCourse.id());
cs.subUniversityId(selectedCourse.selectedSubUniversity().id());
selectedCourse.courseSchedules.push(cs);
save();
}
};
var removeSubUniversity = function (selectedCourseSchedule) {
if (selectedCourseSchedule) {
selectedCourseSchedule.entityAspect.setDeleted();
save().then(success).fail(failed).fin(finish);
function success() {
inflateCourseSchedules();
}
function failed(error) {
cancel();
var errorMsg = 'Error: ' + error.message;
logger.logError(errorMsg, error, system.getModuleId(vm), true);
}
function finish() {
}
}
};
The data is correct in the database, so this appears to be a knockout binding issue. What would cause the first value to bind twice?
Below is the key code. If subuniversity hides deleted rows. With subuniversity resolves the duplicate issue. The problem was caused by calling subuniversity().subuniversityname. The () broke the relationship between the bound item and the displayed item. When save was called the id was changed causing knockout to think it was a new item and bind it again causing the displayed collection to get out of sync with the databound collection.
<!-- ko if: subUniversity -->
<article>
<div>
<!-- ko with: subUniversity -->
<span style="margin-right: 10px" data-bind="text: subUniversityName"></span>
<!-- /ko -->
<button class="btn btn-danger" data-bind="click: $root.removeSubUniversity" style="float:right"><i class="icon-remove"></i></button>
</div>
<br />
</article>
<!-- /ko -->
Below is some more useful code. The add function adds the item to the collection waiting for save to be called. Since you have the cancel button it seems like it would be good if it was honored. The delete function cancels the add if the item was added in this context. Otherwise it sets it as deleted. Thanks to the if statement in the markup deleted items disappear from the list.
var addSubUniversity = function (selectedCourse) {
if (selectedCourse) {
var cs = datacontext.createCourseSchedule();
cs.courseId(selectedCourse.id());
cs.subUniversityId(selectedCourse.selectedSubUniversity().id());
selectedCourse.courseSchedules.push(cs);
}
};
var removeSubUniversity = function (selectedCourseSchedule) {
if (selectedCourseSchedule) {
if (selectedCourseSchedule.entityAspect.entityState.isAdded()) {
selectedCourseSchedule.entityAspect.rejectChanges();
course().courseSchedules.remove(selectedCourseSchedule);
}
else
{
selectedCourseSchedule.entityAspect.setDeleted();
}
}
};
Related
When the button is pressed, we clone the email field. Checkbox values conflict. How can I solve this problem? I would be glad if you help. I hope I can.
$('.add-extra-email-button').click(function() {
$('.clone_edilecek_email').clone(true, true).appendTo('.clone_edilen_email');
$('.clone_edilen_email .clone_edilecek_email').addClass('single-email remove-email');
$('.single-email').append('<div class="btn-delete-branch-email"><button class="remove-field-email btn btn-danger"><i class="fas fa-trash"></i></button></div>');
$('.clone_edilen_email > .single-email').attr("class", "remove-email");
$('.clone_edilen_email input').each(function() {
if ($(this).val() == "") {
$(".add-extra-email-button").attr("disabled", true);
} else {
$(".add-extra-email-button").attr("disabled", true);
}
$(".remove-email:last").find('.email-address').val('');
});
});
<div class="col-md-6">
<div class="clone_edilecek_email">
<div class="form-group">
<label for="name">E-posta</label>
<div class="input-group">
<input type="email" class="form-control email-address" name="email[]" placeholder="E-Posta giriniz">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary">
<input type="checkbox" name="ban[]" value="1" autocomplete="off">
<span class="fas fa-ban"></span>
</label>
<label class="btn btn-secondary">
<input type="checkbox" name="circle[]" autocomplete="off">
<span class="fas fa-exclamation-circle"></span>
</label>
</div>
</div>
</div>
</div>
<div class="text-left">
<button type="button" class="add-extra-email-button btn btn-success" disabled><i class="fas fa-plus"></i></button>
</div>
<div class="clone_edilen_email"></div>
</div>
you must set the index between the brackets, like this:
<input type="checkbox" name="circle[0]" autocomplete="off"> <span class="fas fa-exclamation-circle"></span>
Why? otherwise only the selected checkboxes will be send and the backend. The browser will only send that 2 checkboxes are checked to the backend/server. the server than has no idea which of the checkbox indexes where checked. thats why in the frontend you need to provide an index for each checkbox.
Warning: not all backends understand these kind of form names (but most do).
you could do this like this::
var index =1;
$('input').each(function(inputElement) {
// execute the function for each input element. (might want to do the same for select elements.
// take the name of that element
var name = $(inputElement).prop('name');
// replace [] with the index you want// (warning this only works if you dont use multi dimensional arrays.
var newName = name.relace('[]','['+index+']');
// replace the old name with the new name.
$(inputElement).prop('name',newName);
});
note you can use a function like this:
function setIndeces(container, index){
$('input',container).each(function(inputElement){
var name = $(inputElement).prop('name');
var newName = name.relace('[]','['+index+']');
$(inputElement).prop('name',newName);
});
}
setIndeces($('newAddedDiv', 1);
When I press accept button task should be updated with new value which I wrote in input.Sad thing it doesnt work like I want.Do i need to add id`s to array with objects?
or maybe there is some good method to send a object to function.Here`s the code:
<tbody>
<tr ng-repeat="task in tasks">
<td>
<button class="btn btn-danger" ng-click="deleteTask(task)">Delete</button>
<!-- $index-->
</td>
<td>{{task.taskName}}</td>
<td>
<input type="checkbox" ng-model="statusCheck"> </td>
<td style="{{setStyleToTd(statusCheck)}}">{{statusChecker(statusCheck)}}</td>
<td>
<button class="btn btn-primary" ng-click="editTask(task)">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-offset-8 edit-box" ng-show="editBoxShow">
<form action="" class="form-inline">
<div class="form-group">
<lable>Edit task here:</lable>
<div class="input-group">
<input type="text" class="form-control" ng-model="editTaskInput"> </div>
<button type="button" class="btn btn-success" ng-click="acceptEdit()">Accept</button>
</div>
</form>
</div>
$scope.editTask = function(taskToEdit) {
$scope.editBoxShow = true;
$scope.editTaskInput = taskToEdit.taskName;
}
$scope.acceptEdit = function() {
$scope.editBoxShow = false;
$scope.taskToEdit = $scope.editTaskInput;
}
You can capture the index of your table data while clicking edit button and then update the table data by using this index in acceptEdit function.
$scope.editTask = function (taskToEdit) {
$scope.selectedIndex=$scope.tasks.indexOf(taskToEdit);
$scope.editBoxShow = true;
$scope.editTaskInput = taskToEdit.taskName;
}
$scope.acceptEdit = function () {
$scope.editBoxShow = false;
$scope.tasks[$scope.selectedIndex].taskName=$scope.editTaskInput;
}
Noob question but I can get fields to render in Vue but not sure how to delete my fields. I added an index option in the v-for directives but not sure what to do after that. Thanks!
Here is a working JSFiddle: https://jsfiddle.net/xu55npkn/
<body>
<div id="app"></div>
<script>
const createNewOption = () => {
return {
text: '',
isAnswer: false
}
}
const createNewQuestion = () => {
return {
text: '',
options: [createNewOption()]
}
}
var vm = new Vue({
el: '#app',
template: `<div class="quiz-builder container">
<div v-for="question in questions">
<div class="input-group">
<input type="text" class="form-control" v-model="question.text" placeholder="Enter a question">
<span class="input-group-btn">
<button class="btn btn-danger" type="button">X</button>
</span>
<span class="input-group-btn">
<button class="btn btn-secondary" type="button" #click="addOption(question)">Add an option</button>
</span>
</div>
</br>
<div class="input-group" v-for="(option, index) in question.options" style="margin-bottom: 20px">
<span class="input-group-addon">
<input type="checkbox" v-model="option.isAnswer">
</span>
<input type="text" class="form-control" v-model="option.text" placeholder="Enter an option">
<span class="input-group-btn">
<button class="btn btn-danger" type="button">X</button>
</span>
</div></br>
</div>
<button class="btn btn-default" #click="addQuestion" :disabled="questions.length >= 5 ? true : false">
Add another question
</button>
<button class="btn btn-primary" style="background-color: #ffcc00; border: #ffcc00">
Create quiz
</button>
</div>`,
data () {
return {
questions: [createNewQuestion()],
showQuestions: false,
}
},
methods: {
addQuestion () {
this.questions.push(createNewQuestion())
},
removeQuestion (index) {
this.questions.shift(index)
},
addOption (question) {
question.options.push(createNewOption())
}
}
})
</script>
Based on your updated question, you have already solved for removing questions, although yev's answer is a much better way for removing questions.
To remove options, you need to add a new handler for removeOption that takes in both the question (which you are iterating over) and the option (which you are iterating over. Vue handles both of these scenarios for you. You can then find the index of the option and splice the array. See this fiddle.
template:
<button class="btn btn-danger" type="button" #click="removeOption(question, option)">
X
</button>
component:
removeOption (question, option) {
var index = question.options.indexOf(option);
if (index > -1) {
question.options.splice(index, 1);
}
}
Your delete button should look like:
<div v-for="(question, i) in questions">
<div>
<input v-model="question.text">
<span>
<button #click=removeQuestion(i)>X</button>
</span>
<span>
<button #click="addOption(question)">Add an option</button>
</span>
</div>
</div>
Notice I've added i (index) in your for loop and click handler for X button.
Your remove function will look like:
removeQuestion (index) {
this.questions.splice(index, 1);
}
Array.shift will remove only first item in the array which is not exactly what you want :)
I'm banging my head against the wall here. I'm using ng-repeat to populate a table. Inside each row i have 2 buttons, one for updating the row content and for uploading files. The upload button opens a bootstrap modal window, where the user selects the files and clicks on submit.
The submit button uses ng-click to run a function which uses $index as parameter. But the $index value is always the same no matter which row is selected.
The thing I don't understand is that I use the exact same syntax (although outside of a modal window) on my update button, which works just fine.
HTML:
<tr ng-repeat="item in items | filter:search " ng-class="{'selected':$index == selectedRow}" ng-click="setClickedRow($index)">
<td>{{$index}}</td>
<td ng-hide="idHidden" ng-bind="item.Id"></td>
<td ng-hide="titleHidden">
<span data-ng-hide="editMode">{{item.Title}}</span>
<input type="text" data-ng-show="editMode" data-ng-model="item.Title" data-ng-required />
<td>
<button type="button" class="btn btn-primary uploadBtn" data-ng-show="editMode" data-toggle="modal" data-target="#uploadModal">Upload file <i class="fa fa-cloud-upload"></i></button>
<!-- Upload Modal -->
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="uploadModalLabel">Options</h3>
</div>
<div class="modal-body">
<h4>Upload Documents</h4>
<form>
<div class="form-group">
<select data-ng-model="type" class="form-control" id="fileTypeSelect">
<option value="Policy">Policy</option>
<option value="SOP">SOP</option>
</select>
<br>
<div class="input-group"> <span class="input-group-btn">
<input type="file" id="file">
</span>
</div>
<br>
<button type="button" class="btn btn-default" data-ng-click="uploadAttachment($index, type)">Upload</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<button type="button" data-ng-hide="editMode" data-ng-click="editMode = true;" class="btn btn-default pull-right">Edit <i class="fa fa-pencil-square-o"></i></button>
<button type="button" data-ng-show="editMode" data-ng-click="editMode = false; updateItem($index)" class="btn btn-default">Save</button>
<button type="button" data-ng-show="editMode" data-ng-click="editMode = false; cancel()" class="btn btn-default">Cancel</button>
</td>`
JS:
$scope.uploadAttachment = function executeUploadAttachment(index, type) {
var listname = "Risk Register";
var id = $scope.items[index].Id;
console.log(indexID);
readFile("uploadControlId").done(function(buffer, fileName) {
uploadAttachment(type, id, listname, fileName, buffer).done(function() {
alert("success");
}).fail(function() {
alert("error in uploading attachment");
})
}).fail(function(err) {
alert("error in reading file content");
});
}
So the function uploadAttachment($index, type) which is triggered by ng-click doesn't pass the right index number. It always passes the same, no matter what row it is clicked in.
I have omitted some of the code that is irrelevant. If needed i can provide the whole thing.
Any suggestions to what I am missing?
Edit:
I have tried to implement DonJuwe suggestions.
I have added this inside my controller:
$scope.openModal = function(index) {
var modalInstance = $modal.open({
templateUrl: 'www.test.xxx/App/uploadModal.html',
controller: 'riskListCtrl',
resolve: {
index: function() {
return index;
}
}
});
};
This is my modal template:
<div class="modal-header">
<h3 class="modal-title" id="uploadModalLabel">Options</h3>
</div>
<div class="modal-body">
<h4>Upload Documents</h4>
<form>
<div class="form-group">
<select data-ng-model="type" class="form-control" id="fileTypeSelect">
<option value="Policy">Policy</option>
<option value="SOP">SOP</option>
</select>
<br>
<div class="input-group"> <span class="input-group-btn">
<input type="file" id="file">
</span>
</div>
<br>
<button type="button" class="btn btn-default" data-ng-click="uploadAttachment($index, type)">Upload</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
And finally my function which resides inside RiskListCtrl (the only controller i use):
$scope.uploadAttachment = function executeUploadAttachment(index, type) {
var listname = "Risk Register";
var id = $scope.items[index].Id;
console.log(indexID);
readFile("uploadControlId").done(function(buffer, fileName) {
uploadAttachment(type, id, listname, fileName, buffer).done(function() {
alert("success");
}).fail(function() {
alert("error in uploading attachment");
})
}).fail(function(err) {
alert("error in reading file content");
});
}
It seems that $scope.items[index].Id is empty. Error: Cannot read property 'Id' of undefined
The modal window has its own scope. That means you need to resolve data you want to pass into the modal's scope. To do so, use resolve within the modals open(options) method.
Before I will give you an example, I want to suggest having only one modal for all your table items. This will let you keep a single template where you can easily use id (now, you create a template for each of your table items which is not valid). Just call a controller function and pass your $index:
<button type="button" class="btn btn-primary uploadBtn" data-ng-show="editMode" ng-click="openModal($index)">Upload file <i class="fa fa-cloud-upload"></i></button>
In your controller, create the modal instance and refer to the template:
$scope.openModal = function(index) {
var modalInstance = $modal.open({
templateUrl: 'myPath/myTemplate.html',
controller: 'MyModalCtrl',
resolve: {
index: function() {
return index;
}
}
});
};
Now you can access index in your MyModalCtrl's scope by injecting index:
angular.module('myModule', []).controller('MyModalCtrl', function($scope, index) {
$scope.index = index;
});
Since, you are getting the index value outside model then you can also use ng-click and then call a function in your controller and store the index value in a temporary variable and then when you are using submit button then just take make another variable and assign the value of temporary variable to your variable. for example:
<button type="button" data-ng-show="editMode" data-ng-click="editMode = false; updateItem($index)" class="btn btn-default">Save</button>
and then make a function in your controller
$scope.updateItem = functon(index)
{
$scope.tempVar = index;
}
now use the value of tempVar in you function
$scope.uploadAttachment = function executeUploadAttachment(index, type) {
var index = tempVar; //assign the value of tempvar to index
var listname = "Risk Register";
var id = $scope.items[index].Id;
console.log(indexID);
readFile("uploadControlId").done(function(buffer, fileName) {
uploadAttachment(type, id, listname, fileName, buffer).done(function() {
alert("success");
}).fail(function() {
alert("error in uploading attachment");
})
}).fail(function(err) {
alert("error in reading file content");
});
}
Here is a code snipped of view and controller. Here what I want just click on save button I mean
<input class="btn btn-primary btn-lg mr-r15" type="button"
ng-click="$parent.saveReleaseNotes(latestReleaseNotes,isEditabel);$apply()"
ng-disabled="formSubmitted" value="{{saveButton}}" />
A request will be sent to server and when get success then tag must be hidden and tag will show but not working
HTML:
<div class="clearfix mr-b25 bdr-b">
<h6 class="mr-tb20">Release Notes
<a ng-if="isEditabel == false" href="" ng-click="$parent.isEditabel = true"
class="f-14 semi-bold mr-l15"> Edit </a>
</h6>
</div>
<p ng-show="!isEditabel" class="form-control-static f-14">{{latestReleaseNotes}}</p>
<div ng-show="isEditabel">
<textarea ng-model="latestReleaseNotes" rows="3" columns="15"
class="form-control" ng-disabled="formSubmitting"></textarea>
<br /> <input class="btn btn-primary btn-lg mr-r15" type="button"
ng-click="$parent.saveReleaseNotes(latestReleaseNotes,isEditabel);$apply()"
ng-disabled="formSubmitted" value="{{saveButton}}" /> <input
type="button" ng-click="isEditabel = false" id="backLink"
class="btn btn-link btn-lg" value="Cancel">
</div>
In controller:
$scope.saveReleaseNotes = function(latestReleaseNotes,isEditabel) {
$scope.backgroundWorking = true;
$scope.saveButton = 'Updating...';
$http({
url: '/apps/'+$scope.app.id+'.json',
method: "PUT",
data: {
releaseNotes: latestReleaseNotes,
appBuildId:$scope.app.latestBuild.id
},
headers: {
'Content-Type': 'application/json'
}
}).success(function(data, status, headers, config) {
if (data.result == "success") {
flash.showSuccess(data.message);
$scope.isEditabel = false;
} else {
flash.showError(data.message);
$scope.latestReleaseNotes = $scope.app.latestBuild.releaseNotes;
}
$scope.backgroundWorking = false;
$scope.saveButton = 'Save';
}).error(function(data, status, headers, config) {
flash.showError("Error occued while updating release notes");
$scope.backgroundWorking = false;
$scope.saveButton = 'Save';
});
}
but model isEditable is not updated in view. I need hide <div> tag and show <p> tag on success.I'm trying by $scope.isEditabel = false; but it is not working.
I think your issue is you are toggling between using $parent.isEditable and isEditable. My first suggestion is to be consistent. Idealy isEditable is contained within the scope that you are using it. It seems a little odd to always be referencing "isEditable" of a parent.
If isEditable truly is contained within the parent you need to be careful about setting $scope.isEditable = true. You can end up redeclaring it on the child scope. I always suggest using functions like setIsEditable(true) and define that in the parent scope.
Trying to create a fiddle for you based on what you have given us.. but I think the below code would work.
//Parent scope
$scope.isEditabel = false;
$scope.setIsEditabel = function(value){
$scope.isEditabel = value;
}
<!-- updated html -->
<div class="clearfix mr-b25 bdr-b">
<h6 class="mr-tb20">Release Notes
<a ng-if="$parent.isEditabel == false" href="" ng-click="$parent.setIsEditabel(true)"
class="f-14 semi-bold mr-l15"> Edit </a>
</h6>
</div>
<p ng-show="!$parent.isEditabel" class="form-control-static f-14">{{latestReleaseNotes}}
</p>
<div ng-show="$parent.isEditabel">
<textarea ng-model="latestReleaseNotes" rows="3" columns="15"
class="form-control" ng-disabled="formSubmitting">
</textarea>
<br />
<input class="btn btn-primary btn-lg mr-r15" type="button"
ng-click="$parent.saveReleaseNotes(latestReleaseNotes,$parent.isEditabel);$apply()"
ng-disabled="formSubmitted" value="{{saveButton}}" /> <input
type="button" ng-click="$parent.isEditabel = false" id="backLink"
class="btn btn-link btn-lg" value="Cancel"/>
</div>
the better solution(always debatable)*:
I created a myModel this isn't necessary but it does clean things up a bit, esp. if you ever have to have multiple editable things on the same page.
We dont have to worry about passing around isEditable to the save, we shouldn't have to worry about apply. General suggestion is to make your html dumber..
//child scope
$scope.myModel = {
latestReleaseNotes: latestReleaseNotes,
isEditabel: false
}
$scope.saveReleaseNotes = function(){
//do save
//we have everything we need on $scope (myModel, isEdiabel, etc.)
}
<!-- updated html -->
<div class="clearfix mr-b25 bdr-b">
<h6 class="mr-tb20">Release Notes
<a ng-if="myModel.isEditabel == false" href="" ng-click="myModel.isEditabel = true"
class="f-14 semi-bold mr-l15"> Edit </a>
</h6>
</div>
<p ng-show="!myModel.isEditabel" class="form-control-static f-14">{{myModel.latestReleaseNotes}}
</p>
<div ng-show="myModel.isEditabel">
<textarea ng-model="myModel.latestReleaseNotes" rows="3" columns="15"
class="form-control" ng-disabled="formSubmitting">
</textarea>
<br />
<input class="btn btn-primary btn-lg mr-r15" type="button"
ng-click="saveReleaseNotes(myModel);"
ng-disabled="formSubmitted" value="{{saveButton}}" /> <input
type="button" ng-click="myModel.isEditabel = false" id="backLink"
class="btn btn-link btn-lg" value="Cancel"/>
</div>