re render click event for added element backbone - php

I'm currently new in backbone.js. I use CodeIgniter as my PHP Framework.
I'm developing an existing system which uses a backbone.js library. My problem is easy if i will do it in jquery but since this uses a backbone.js, I should do it the same way. This what happens.
Once the page is loaded, i will populate the users inside a select box. then with a button 'ADD'. I will populate also the uses already added inside the ul element to list all the users with an x anchor if you want to delete the user.
I append the user in the list after success ajax in create warehouse user. I use jquery to append. Now the click event to delete is not working because backbone already done rendering the views. How to re render backbone click event for newly added element?
Below is my sample code
HTML CODE
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-list"></i> User Access
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<form method="POST" action="<?php echo base_url();?>warehouse/user" id="add-warehouse-user-form">
<div class="col-lg-4">
<div class="form-group">
<label for="users">Available Users </label>
<input type="hidden" name="wh_id" id="wh_id" value="<?php echo $warehouse->wh_id; ?>" />
<select class="form-control" name="users" id="users">
<?php if(!empty($users)){
foreach($users as $row){
//if(in_array('Head',$row->user_access) AND $row->status == 'ACTIVE'){
echo '<option value="'.$row->user_id.'">'.$row->first_name.' '.$row->last_name.'</option>';
//}
}
}?>
</select>
</div>
</div>
<div class="col-lg-1">
<div class="form-group">
<label for="users"> </label>
<input type="submit" class="btn btn-success form-control" value="Add" />
</div>
</div>
</form>
</div>
</div><br />
<div class="row">
<div class="col-lg-12">
<div class="col-lg-5">
<ul class="list-group" id="list_user">
<?php if(!empty($userlist)){
foreach ($userlist as $ul) {
echo '<li class="list-group-item">'.$ul['name'].' <span class="badge badge-delete"><a class="deleteUser" href="#" data-id="'.$ul['module_id'].'" data-uid="'.$ul['id'].'" data-name="'.$ul['name'].'">x</a></span></li>';
}
} ?>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
Warehouse model
var WarehouseUserModel = Backbone.Model.extend({
urlRoot: Config.apiBaseUrl + "/warehouse/user",
defaults: {
wh_id: "",
users: ""
}
});
var WarehouseUserDelModel = Backbone.Model.extend({
urlRoot: Config.apiBaseUrl + "/warehouse/user"
});
Warehouse View
var WarehouseView = Backbone.View.extend();
//-------------- add user to warehouse --------------//
var WarehouseUserAddNewView = Backbone.View.extend({
el: "#add-warehouse-user-form",
loaderEl: "#form-loader",
events:{
"submit": "createWarehouseUser"
},
createWarehouseUser: function (e) {
var self = this;
e.preventDefault();
var formData = Util.getFormData($(self.el));
var warehouseUser = new WarehouseUserModel(formData);
//disable form
$(self.el).find(" :input").prop("disabled", true);
//show loader
$(self.loaderEl).removeClass("hidden");
warehouseUser.save({}, {
success: function (model, res, options) {
if (res.status == "error") {
Util.formError(res.data);
var errorTemplate = _.template($("#toast-error-template").html());
toastr.error(errorTemplate({errors: res.data}));
//re-enable form
$(self.el).find(" :input").prop("disabled", "");
//hide loader
$(self.loaderEl).addClass("hidden");
} else {
//location.href = res.data.redirectUrl;
/** append user to list and remove from select box **/
$('#list_user').append('<li class="list-group-item">'+$("#users option[value='"+$("#users").val()+"']").text()+' <span class="badge badge-delete"><a class="deleteUser" href="#" data-id="'+res.data.id+'" data-uid="'+$("#users").val()+'" data-name="'+$("#users option[value='"+$("#users").val()+"']").text()+'">x</a></span></li>');
$("#users option[value='"+$("#users").val()+"']").remove();
//re-enable form
$(self.el).find(" :input").prop("disabled", "");
//hide loader
$(self.loaderEl).addClass("hidden");
}
}
});
},
initialize: function () {},
});
//-------------- remove user from warehouse --------------//
var WarehouseUserDeleteView = Backbone.View.extend({
el: ".deleteUser",
events:{
"click": "deleteWarehouseUser"
},
deleteWarehouseUser: function (e) {
elem = $(e.currentTarget)[0];
var self = this;
e.preventDefault();
var warehouseUser = new WarehouseUserDelModel({'id':$(elem).data('id')});
warehouseUser.destroy({
success: function (model, res, options) {
console.log(model);
console.log(res);
console.log(options);
if (res.status == "error") {
Util.formError(res.data);
var errorTemplate = _.template($("#toast-error-template").html());
toastr.error(errorTemplate({errors: res.data}));
} else {
/** append user to select box and remove from lists **/
$("#users").append("<option value='"+$(elem).data('uid')+"'>"+$(elem).data('name')+" </option>");
$(elem).parents('li.list-group-item').remove();
}
}
});
},
initialize: function () {},
});
new WarehouseUserAddNewView();
new WarehouseUserDeleteView();

The actual code that renders your list of users will probably always be jquery - backbone does not come with default render functionality. However, it is common practice to put code that will render the entire contents of the el in a render method of the view. That way it can be called initially, and then called again whenever conditions have changed.
It looks like your general approach is to use each view a bit like an event handler. Each has a single event attached, with one sizeable method to do some work. That's fine as long as it's working for you, but you can also have a more complicated view than handles multiple functions. I might recommend you use your WarehouseView to keep a list of users, and handles both a reusable render, and the delete method you have already written:
var WarehouseView = Backbone.View.extend({
el: '#list_user',
initialize: function(options) {
this.users = (options && options.users) || [];
// render once on intialize
this.render();
},
events: {
// listen for delete clicks on contained elements
'click .deleteUser': 'deleteWarehouseUser',
},
deleteWarehouseUser: function(ev) {
// your same method code should work here
},
render: function() {
// render the list of users you have
this.$el.html('');
for (var i = 0; i < this.users.length; i++) {
// use jquery to add the user to your list as a <li>
}
}
});
var warehouseView = new WarehouseView();
If you use this approach, then inside your add new user method you can do things like:
// ... create your user variable
warehouseView.users.push(user);
warehouseView.render();
The delete (client-side) can also consist of removing a particular user from your view's list, and then rerendering.
You can arrange backbone objects pretty much any way you like, but I hope this recommendation helps. Also note that once you feel more comfortable, creating a Backbone Collection for your users is the more "backbone-y" way to do things rather than a plain array.

Related

how to make a multipage form

i'm making a website for online exams. i made 1 question per page and i made a custom pagination but now i dont know how to make when i move to the next question i save the answer of the previous question so i can submit all the answers to the databse
this is the view code
<form method="POST" action="{{route("answer.store")}}">
#csrf
<div class="content" v-for="question in questions">
<div v-if="post.qtype === 'choose'">
<div class="font-weight-bold p-4"> #{{question.qname}}</div>
<div v-for="choice in question.choices">
<div class="p-4">
<input type="radio" class="form-check-input" style="display: block" id="radio" :name="'answers[q' + post.id + '][]'" :value="choice">
<label class="form-check-label" for="radio">#{{choice}}</label>
</div>
</div>
</div>
</div>
{{ Form::hidden('exam_id', Crypt::encrypt($exam->id)) }}
<input class='btn btn-primary' v-if="pagination.current_page == pagination.last_page" id='submit-btn' type="submit">
</form>
expected result : all the answers get submited
actual result : only last answer get submited
**Note: im using default laravel pagination but i use json scripts and axios to move throw pagination without refreshing page **
If I understood correctly (and you are also using Vue.js for frontend), I suggest using one of the 2 following approaches:
Use Vuex - Create 1 big json object that will be filled after submitting every step and finally, send it via axios to your API
Have 1 parent component to hold the big object in its data and child component which will represent current step of the multi-step form. Each child step should validate its inputs and then emit the data to parent component. After coming to the last step, just send the request from the parent.
Bonus: I strongly recommend taking a look at https://v2.vuejs.org/v2/guide/components-dynamic-async.html for child components with keep alive tag in case you want to be able to get to previous step as well.
General architecture for the second approach (I didn't test it but you should get the big picture):
// Parent.vue
<template>
<div>
<keep-alive>
<component v-bind:is="actualStep" #stepSubmitted="handleStepSubmitted"></component>
</keep-alive>
</div>
</template>
<script>
export default {
components: {
Step1: () => import("./Step1"),
Step2: () => import("./Step2")
},
data() {
return {
resultToSend: [],
formSteps: ["Step1", "Step2"],
actualStep: "Step1"
};
},
methods: {
handleStepSubmitted(dataFromStep) {
resultToSend.push(dataFromStep);
if (formSteps.indexOf(actualStep) === formSteps.length - 1) {
this.submitData();
} else {
this.actualStep = this.nextStep();
}
},
nextStep() {
return this.formSteps[this.formSteps.indexOf(actualStep) + 1];
},
submitData() {
// send your post request
}
}
};
</script>
// Step1.vue
<template>
<div>
<input v-model="data1" type="text" />
<button #click="submitstep">Next</button>
</div>
</template>
<script>
export default {
data() {
return {
data1: ""
};
},
methods: {
submitStep() {
this.$emit("stepSubmitted", data1);
}
}
};
</script>
// Step2.vue
<template>
<div>
<input v-model="data2" type="text" />
<button #click="submitstep">Next</button>
</div>
</template>
<script>
export default {
data() {
return {
data2: ""
};
},
methods: {
submitStep() {
this.$emit("stepSubmitted", data2);
}
}
};
</script>
You have to create an array with a key-value pair then write a method that pushes the ids and answers into the array on every page change(If a user wants to turn back and change the question then you need to implement CRUD actions for this array). If the user achieved the last page then post your array into your database with axios.
Use JavaScript to submit the form when the next button is clicked:
var nextBtn = document.getElementById("nextBtn");
nextBtn.addEventListener("click", function(e){
e.preventDefault();
let form = document.getElementById("form");
form.submit();
return true;
});

Knockout foreach input value user updated value

I have data-binding listing working fine. In listing, I have first column for order level. All data is coming from Ajax based server side.
HTML CODE:
<div class='general_content' id="designations_items">
<div class="listing-grid">
<div class="listing_wrapper">
<!--Listing Columns-->
<div class="column heading option_cols">
Order Level
</div>
<div class="column heading desig_cols">
Designation Name
</div>
<div class="column heading desig_cols">
Job Description
</div>
<div class="column heading option_cols">
Options
</div>
<!--Listing Data-->
</div>
<form name="desig_order_level" action="<?php echo $url?>manage/designation_order_level/" id="desig_order_level">
<div class="listing_wrapper" data-bind="foreach: Designations">
<div class="column data_display option_cols">
<input name="orders[]" data-bind="value: Desig_Order" class="fancyInput_smaller">
</div>
<div class="column data_display desig_cols" data-bind="text: Desig_Name"></div>
<div class="column data_display desig_cols" data-bind="text: Desig_Desc"></div>
<div class="column data_display option_cols">
Remove
</div>
</div>
</form>
</div>
</div>
This is my JS code:
function GetDesignations(handleData) {
$.ajax({
url: 'get_designations.php',
type: "post",
data: '',
dataType: 'json',
success: function(data){
handleData(data);
},
error:function(data){
alert('Failed');
}
});
}
$(function () {
var Designation_ViewModel = function() {
var self = this;
self.Designations = ko.observableArray();
self.update = function() {
GetDesignations(function(output){
self.Designations.removeAll();
$.each(output, function (i) {
self.Designations.push(new deisgnation_data_binding(output[i]));
});
});
};
self.addnewItem = function () {
var newitem = JSON.parse('{"Name":"'+$("#Name").val()+'", "Desig_desc":"'+$("#Desig_desc").val()+'"}');
self.Designations.push(
new deisgnation_data_binding(newitem)
);
};
self.removeToDoItem = function(item) {
self.Designations.remove(item);
};
};
var Designation_ViewModel = new Designation_ViewModel();
var y = window.setInterval(Designation_ViewModel.update,1000);
ko.applyBindings(Designation_ViewModel, document.getElementById("designations_items"));
});
var deisgnation_data_binding = function (data) {
return {
Desig_Order: ko.observable(data.Desig_Order),
Desig_Name: ko.observable(data.Desig_Name),
Desig_Desc: ko.observable(data.Desig_Desc)
};
};
After few seconds, listing is being auto updated for new records... In that case, order level is also getting new database entries for each record.
Issue is that at user side I cannot input new values in order level input box to update, when I select the text box to enter new value all listing gets update due to this reason unable to let user update order level.
As I see your records are updated every 1 second due to
var y = window.setInterval(Designation_ViewModel.update,1000);
I think you should use setTimeout instead of setInterval here. setTimeout will execute update function only once
window.setTimeout(Designation_ViewModel.update,1000);

Better PHP with Json

Hi currently i have an app on Zend php framework, and i heavily using json to fetch data from controller. Now i am not sure whether the way i parse json data into hmtl within javascript are good or not. Below are my sample code.
controller:
public function searchAction()
{
$search = $this->getRequest()->getParam('search');
$user = new Application_Model_User();
$userMapper = new Application_Model_Mapper_UserMapper();
$usersearch = $userMapper->findByString($search);
for($i=0; $i<count($usersearch); $i++)
{
$usersearch[$i]['pic'] = $this->view->getLoginUserImage($usersearch[$i]['social_id'],$usersearch[$i]['login_type'],null,null,square);
}
$this->_helper->json($usersearch);
}
View: member.phtml
<div class="container">
<div class="clearfix page-header">
<h1 class="heading">Member Search</h1>
</div>
<div class="clearfix layout-block layout-a">
<div class="column column-alpha member-search-container">
<div class="search-input-container clearfix">
<form action="/member_search?query=" class="member-search-form" id="member-search-form" method="get" name="member_search_form" data-component-bound="true">
<fieldset>
<legend>Member Search</legend>
<label for="name_field">
<strong> Name</strong>
</label>
<span class="formNote">
(e.g. Bob Smith, Bob S.)
</span><br>
<input type="hidden" name="action_search" value="Search">
<input class="name-field" id="story-title" name="query" size="90" type="text" placeholder="search" autocomplete="off" style="width:220px;">
<div id="search-box"></div>
<div class="auto-name" style="display: none;"></div>
</fieldset>
</form>
</div>
<div class="member-search-results">
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function(){
});
$('#story-title').keyup(function(e){
var searchbox = $(this).val();
if(searchbox == '')
{
$(".similar_story_block").hide();
}else {
$.ajax({
url:"<?= $this->baseUrl('index/search')?>",
data:{'search':$('#story-title').val()},
dataType:"json",
type:"POST",
cache:false,
success:function(data){
if(data.length > 0)
{
var divHtml = '<div class="similar_story_block" style="display: block;">'+
'<div class="head">'+
'<p>People</p>'+
''+
'</div>'+
'<ul>';
for(var count=0; count<data.length;count++)
{
if(data[count]['reviews_num'] != null )
{
data[count]['reviews_num']
}
else
{
data[count]['reviews_num'] = 0
}
divHtml+='<li>'+
'<a class="pushstate no-confirm" href="' + baseUrl + 'user/' + data[count]['user_unique_name'] + '">'+
'<div class="image">'+
'<img alt="" src="'+data[count]['pic']+'">'+
'</div>'+
'<div class="fleft col-400">'+
'<p>'+ data[count]['name'] +'</p>'+
'<span>'+data[count]['reviews_num']+' Reviews</span>'+
'</div>'+
'</a>'+
'</li>';
}
divHtml += '</ul></div>';
$("#search-box").html(divHtml);
$(".search-box").show();
}
else {
$("#search-box").html('');
$(".search-box").hide();
}
}
}) }
});
function closeSearchBox(event)
{
disabledEventPreventDefault(event);
$(".similar_story_block").hide();
}
</script>
Currently the above code will do a live query of members who already signup to the site. The code work very well, but i am not sure if this is right way of doing. Below is how it looks on chrome debug console
it seems i am exposing too much of details. It would be appreciated if anyone can suggest a better way of fetch a data from controller, or how it can be done by using partial template.
Thanks for your help !!!
You can either render your template via PHP and send HTML down the wire, or you can send JSON and render your template using JavaScript.
I would suggest the latter using something like HandlebarsJS.
Define the HTML template:
<script id="member-template" type="text/x-handlebars-template">
<p>Name: {{ name }}</p>
<p>Role: {{ role }}</p>
</script>
Our example JSON data:
var data = {"name": "Bob", "role": "Manager"};
Render our template with our JSON data:
var template = Handlebars.compile($("#member-template").html());
var html = template(data);
The html variable will be your compiled HTML that you can insert witin your <div class="member-search-results"> div.
I think if your project has a lot of visitors and this operation uses often you can relay this function to them. It's could reduce little load on the server. But it could work only if you have very many visitors (pageview). But if your project not so big you can't feel the difference and "PHP way" could be easier on my mind.

Jquery not considering new div that is added by Jquery + php + jquery

I have a php page where I add and delete items from database using Jquery + PHP + AJAX.
Now I am able to delete and add when that page loads for the first time.
Now if I first add an element; which in turn adds record to the DB and then updates the div that contains all the listing of divs.
Example:
<div id="all_items">
<div id= "item_1">
<a id="delete_link">...</a>
</div>
<div id= "item_2">
<a id="delete_link">...</a>
</div>
.... Upto Item n
</div>
Now I replace the div with id all_items.
Now I have jQuery at the bottom of the page which calls ajax on a tag of delete_link.
Situtation is:
When page is loaded I can delete any item from the list.
But if I page load i add new item first. (which will update all_items div) after that if I try to click on delete link. Jquery on click selector event is not fired and which in turn doesn't do delete ajax operation.
I couldn't figure out why this is happening.
Looking for some help here.
EDITED:
Sorry for not writing code earliar.
Following is the jQuery I am talking about.
<script type="text/javascript" >
var jQ = jQuery.noConflict();
jQ(function() {
jQ("#submit_store").click(function() {
var store_name = jQ("#store_name").val();
var dataString = 'store_name='+ store_name;
dataString += '&mode=insert';
if(store_name =='')
{
alert("Please Enter store Name");
}
else {
jQ.ajax({
type: "POST",
url: "<?php echo $mycom_url; ?>/store_insert.php",
data: dataString,
cache: false,
success: function(html){
jQ("#dvstoreslists").html(html);
document.getElementById('store_name').value='';
document.getElementById('store_name').focus();
}
});
}
return false;
});
jQ(".store_delete").click(function() {
var store_id = jQ(this).attr('id');
var id = store_id.split("_");
var dataString = 'store_id='+ id[2];
dataString += '&mode=delete';
var to_delete = "#store_list_" + id[2]
jQ.ajax({
type: "POST",
url: "<?php echo $mycom_url; ?>/store_insert.php",
data: dataString,
cache: false,
success: function(html){
jQ(to_delete).hide("slow");
}
});
return false;
});
});
</script>
So If on page load, I delete then delete on click jquery event is fired. But after adding new store and replacing div of stores with new div. then jQuery on click event is not fired.
My HTML is as below.
<div class="sbBox stores">
<form id="add_storefrm" name="add_storefrm" method="post" action="" >
<div class="dvAddStore">
<div class="dvName" id="store_list">
<input type="text" id="store_name" name="store_name">
<input type="hidden" value="addstore" id="mode" name="mode">
</div>
<div class="btnAddStore">
<input type="submit" name="submit_store" value="Add Store" id="submit_store">
</div>
</div>
<div id="dvstoreslists">
<?php
$query = "SELECT * FROM #__shoppingstore order by store_id desc;";
$db->setQuery($query);
$rows = $db->loadObjectList();
foreach($rows as $row)
{
echo "<div class=dvlist id=store_list_".$row->store_id ."><div class=dvStoreListLeft>";
echo "<div class='slname'><h3>" . $row->store_name . "</h3></div>";
?>
<div class="slDelBtn">
<p id = "p_store_<?php echo $row->store_id ; ?>">
<a id="store_delete_<?php echo $row->store_id ; ?>" class="store_delete" alt="Delete" onclick="DeleteStore(<?php echo $row->store_id ; ?>);" >
</a>
</p>
</div>
</div>
</div>
<?php } ?>
</div>
</form>
</div>
Sorry folks for not posting the code earliar.
the ID should always be unique so use class instead
in your case : <a id="delete_link">...</a> to <a class="delete_link">...</a>
When you replace the contents of #all_items any event handlers that were bound to any descendants will no longer exist. You can use event delegation, using the on method, to solve this:
$("#all_items").on("click", ".delete_link", function() {
//Do stuff
});
Notice that I'm using a class selector (.delete_link) instead of an ID selector for the links. It's invalid to have duplicate IDs in the same document.
Also note that the above will only work if you are using jQuery 1.7 or above. For older versions, use delegate instead:
$("#all_items").on(".delete_link", "click", function() {
//Do stuff
});
This works because DOM events bubble up the tree from their target. So a click on a link which is a descendant of #all_items will bubble up through all of its ancestors and can be captured when it reached #all_items.
use live() instead of .bind()
It seems you are trying to delete dynamically added delete_link so i think you should use
$('id or class').on(click,function(){});

jQuery parent usage

First time using jQuery parent and got confused, here's my html:
<div id="friends_inveni" class="friends_rec" style="">
<br>
<div style="height: 280px; overflow-y: auto;">
<div style="width:150px;float:left;font-size:11px;color:White;">
<input type="checkbox" name="friends" value="3265" id="friend_3265">
<img style="width:24px;vertical-align:middle;" src="https://graph.facebook.com/661382259/picture">
Test1
</div>
<div style="width:150px;float:left;font-size:11px;color:White;">
<input type="checkbox" name="friends" value="3265" id="friend_3265">
<img style="width:24px;vertical-align:middle;" src="https://graph.facebook.com/599567764/picture">
Test2
</div>
</div>
<br clear="all">
<div class="action_new_comment">
<textarea class="action_comment_input" onkeyup="check_height(this);" style="height:36px;"></textarea>
<br clear="all">
<a class="action_btn" style="font-size:11px;" name="comment" id="A1">Send Recommendation</a>
</div>
</div>
Here's my jQuery code:
jQuery(document).ready(function() {
$(".action_btn").live('click', function() {
var friends = new Array();
$(this).parents('.recommend').find('input:checked').each(function(k) { friends[k] = $(this).val(); });
var comment = $(this).parents('.recommend').find('textarea').val();
if (friends.length == 0) {
alert('Please select at least one friend.');
} else {
$.post("update.php",
{ uid: <?php echo $_SESSION['uid']; ?>, mid: <?php echo $_GET['mid']; ?>, friends: friends, comment: comment },
function() {
var newComment = $('<div class="bubble_confirmation"><h1>Recommendation Sent!</h1><br /><br > Send more</div>');
$(this).parents('.recommend').append(newComment.fadeIn());
$(this).parents('.recommend').find("input:checkbox:checked").attr("checked", "");
$(this).parents('.recommend').find('.action_comment_input').val('');
});
}
});
});
Issue is that:
1. I can't get the content of the textbox
2. I can't get the array of userid
Why is this?
It looks as though your HTML class friends_rec should be recommend, judging from your jQuery code.
Also, since we don't know what $(el) is, that should probably be $(this), instead.
Addition to what Herman said you should do:
var $parent = $(this).parents('.recommend:first')
inside of your ajax callback. The :first prevents jQuery from going up the whole tree to look for more elements of that classname after you've already found the element you're looking for and keeping it as a variable reference allows you to not create a new jQuery object each time you need to use it.

Categories