Creating multi-field in-place editor with JavaScript/JQuery - javascript

I am looking for a way to create an in-place editor in JavaScript like JEditable or similar plugins. However, the difference is that my editor would be a multi-field form rather than single field.
Since the same form will be used in different parts, i.e. editing existing items and creating new items, I would like to be able to specify the callback function for each case while not having to duplicate the shared functionality (e.g. validation, cancel button, etc.).
Currently I tried implementing it naively and the code looks awful as event handlers are scattering around the source code instead of being together as a component.
So my question is what is the recommended way to implement this? If there is a jQuery plugin that provide the functionality, that might be the easiest route. Otherwise, how would I structure my code properly?

Seperate the definition of your callbacks and do a lookup in the editable callback to your desired callback
var callbacks = { // define your callbacks here
'field1': function() {
alert("You editted field1");
},
'field2': function() {
alert("You editted field2");
}
}
$("input").each(function(i, o) {
$(o).editable('save.php', {
'callback': function(value, settings) {
// do validation etc.
// then call field-specific callback
if(callbacks[o.name]) { // I'm using 'name', but you could also use 'id' or something else
callbacks[o.name]();
}
}
});
});

The problem is that you need to separate the data from the displayed text. For each chunk of data you display, associate a json dict (written into the page, or pulled with ajax) with the text for each field in the form.
For example, if you're working with names (first, middle, last), you display the name like this:
<span class="editable">Colin M. Hansen</span>
and you write the form like this:
<form class="nameform" style="display: none;">
<input class="first" type="text">
<input class="mi" type="text">
<input class="last" type="text">
</form>
and you have a dict like this, mapped to the inputs in the form:
name1 = {
first: 'Colin',
mi: 'M',
last: 'Hansen'
};
The common code you write switches the form in for the text on the onclick of the span element, and fills each input field with data from the dict. The submit code saves the new data to name1 and to the server, and returns the new display text.

Related

Watch for changes in object with form based and non-form based properties

On a rather big page a user can edit data in various ways:
- Change some "classical" input fields
- Add files via clicking on a button
- Change the order of items via drag and drop
In the HTML, a simplified example might look like such:
<form>
Name: <input ng-model="person.name">
Title: <input ng-model="person.title">
Image: <our-custom-image-uploade-directive ng-model="person.image"/>
Skills: <our-custom-skill-drag-and-drop-directive ng-model="person.skills"/>
<button ng-click="save()">Save</button>
</form>
You see, some edit "facilities" are form-based, some are not.
Now, there is a rather simple task to do: Disable the "save" button if the user didn't change anything or happened to end up with the very same state of data that it was before the user interacted with the data.
Now I'm wondering which is the best way to achieve that.
One way might be to deep watch the whole person object. Like that:
$scope.backupCopyOfPerson = angular.copy($scope.person); // Creae a backup copy of the state before the user changed something
$scope.$watch('person', function (newValue) {
if(newValue && $scope.backupCopyOfPerson) {
if(angular.equals(newValue, $scope.backupCopyOfPerson)) {
$scope.unsavedChanges = false;
}
else {
$scope.unsavedChanges = true;
}
}
}, true);
However, deep watching a big object with a lot of sub-objects etc. might cause some serious performance issues.
Another idea is, using ng-pristine for the vanilla form fields and do in all other directives etc. $setDirty()/$setPristine(). That might be faster, but it's definitely not an elegant solution.
What do you think?
Angular version is 1.58
My take on this:
If you have issue with the watching performance:
You should combine angular form checking for regular input, and add a watcher on the other attributes.
This will save you time & performances as you will watch the variable that are not already
$scope.$watch('person.image', function (newValue) {
$scope.unsavedChanges.image = ($scope.person.image == $scope.backupCopyOfPerson.image)
}}, true);
and then upon save check if your unsavedChanges as at least 1 true

$watch or ng-model binding issue?

I basically have a very complicated set of tabs with various input controls but lets not worry about this for now. For now let consider simple input wtching issue I am baffled by.
For example:
<input type="text" placeholder="Associate some tags please" data-ng-model="tag" data-ng-maxlength="250">
I am try to detect if user has typed something into my input:
$scope.$watch('tag', function () {
//$scope.$watchCollection('tag', function () {
console.log('tag changed');
});
But I seem to get zero response. However, when I perform my save operation I always seem to get the value user typed in.
Is a case of ng-model not binding correctly or is it that I am not $watching it correctly?
Also after I've performed my save operation I try to clear what user typed in for the tag with:
$scope.tag = '';
But that doesn't seem to work for some reason as though $scope.tag doesn't exist.
PS: When I say save operation I am actually performing a array.push() into an object which later gets saved by a service.
For example:
$scope.checkSaveTag = function (tag) {
...
// checked for duplicate tag beforehand
$scope.myForm.Tags.push(tagObj); // complicated form object
$scope.tag = ''; // tag input control
...
};
Any chance that the tag is an object or an array? If that is the case, you'll need to do a deep $watch, e.g:
$scope.$watch('tag', function () {
console.log('tag changed');
}, true);
Try like this
Controller
$scope.form={
tag:''
}
$scope.$watch("form.tag",function(newVal,oldVal){
console.log(newVal);
})
Html
<input type="text" placeholder="Associate some tags please" data-ng-model="form.tag" data-ng-maxlength="250">

jQuery AJAX call from multiple input fields within the 1 script

I'm working on my first HTML form that performs an AJAX HTTP POST using jQuery. When a user makes a change to an input text field and tabs out of the field it triggers the AJAX script which in turn calls a PHP script which performs a database update.
I've got this working successfully for my first input field - I would now like to extend this to a 2nd, 3rd etc input fields but want to try and avoid having multiple scripts that perform very similar functions. I'm new to jQuery and AJAX so learning the syntax as I go.
Here's my input fields:
Manager
Phone
Here's my Javascript that is working on the storeManager input field:
<script type="text/javascript">
$(document).ready(function() {
$("#storeManager").change(function(){
var storeManager = $("#storeManager").val();
$.post('editProject.php', { storeManager: storeManager, id: '1E1DDA14-D2C6-4FC8-BA5F-DBCCC7ABAF7F' }, function(data) {
$("#managerRow").addClass("success");
}).fail(function () {
// no data available in this context
$("#managerRow").addClass("danger");
$("#ajaxAlert").addClass("alert alert-danger");
});
});
});
</script>
I essentially need to branch and pass an additional POST parameter to the editProject.php script so it knows which database field to update, and then conditionally add a class to the appropriate row.
Everything I've tried breaks the script when I try and get it to branch or pass a parameter based on the input field that is being edited. I haven't been able to find any examples that show the correct syntax to have the one script that is called by different input fields - I'm presuming this is possible instead of having multiple versions of the same script acting on different fields.
This works for multiple fields. Just call the same function from different input fields. I just broke your code into two parts.
1. onChange function of each individual field, and
2. function call by passing the field parameters.
<script type="text/javascript">
$(document).ready(function() {
$("#storeManager").change(function(){ yourFunction(this) }):
$("#worker").change(function(){ yourFunction(this) }):
$("#someX").change(function(){ yourFunction(this) }):
yourFunction(field)
{
var value = $(field).val();
var inputId=field.id;
$.post('editProject.php', { inputValue: value, id: inputId }, function(data) {
$('#'+inputId+'Row').addClass("success"); // (this looks like: *#storeManagerRow* ) you can change your Row id's accordingly to make your work easier. Eg: for **#storeManager** make this id as **storeManagerRow**
}).fail(function () {
// no data available in this context
$('#'+inputId+'Row').addClass("danger");
$("#ajaxAlert").addClass("alert alert-danger");
});
});
</script>
You just try to post a value. for example type. Which should contain some value for identify the ajax call.
If it is for login, then add type = 'login'. Then check the value of $_POST['type'] and write php according to it
sample.php
if(isset($_POST['type']))
{
if($_POST['type'] == 'login')
{
//your code goes here
}
}
you can use this kind of code :
$("#storeManager, #Manager, #Phone").change(function(){
You could do something like this using :input or a class that they all have
$(":input").on("change", function(){
var text = $(this).val();
var idOfInput = $(this).attr("id");
//your post to php function using the above variables
});
From this you could post the id of the input to your php script using the idOfInput variable which you could then on the php side use a case switch to do a different query depending on which id is sent to the php
Here is a jsfiddle showing how it works

Dynamic web form, fields can be nested to arbitrary depth, also add/remove fields dynamically

I am trying to build a web form which will have its initial fields determined based on rows in a database. For example, form 1 might have fields A, B, and C. On the other hand, form 2 might have fields D, E, and F. Additionally, there may be hundreds or even thousands of such forms, each with a unique ID.
Now, each one of these fields (A, B, C, etc) might in turn be made up of other fields, which in turn can be made up of yet even more fields. In other words, some fields are actually collections of fields (to an arbitrary depth).
On top of that, the user of the form can choose to add more instances of some of these fields at runtime. Sometimes the user will choose to add a whole new record (all fields) while at other times they may choose to only add another instance of one of the fields (which may itself contain more fields). Also, I need the user to be able to remove any of these fields that they have added.
I've spent about 30 hours so far along with a colleague of mine coming up with a custom Javascript-based solution that involves building our own tree structure alongside the DOM tree, writing some recursive functions, etc but, as the complexity has mounted, it really seems like we were reinventing the wheel here. This strikes me as a problem that must have already been solved at this point.
I'm not very familiar with jQuery but it sounds like it might potentially be a good solution based on what I've heard about it in the past. In other words, I suspect that this might be a problem that jQuery more or less solves "out of the box." But, my initial research into jQuery (on Stack Overflow and on Google in general) gives me the impression that this isn't the case and that a custom solution, using jQuery, needs to be put together.
My question is, what is the easiest way to achieve what I'm looking for? The solution doesn't even have to use jQuery; I just thought that jQuery might be the best way to do it. I'm not looking for someone to write the code for me, just to point me in the right direction since our solutions so far have looked pretty messy.
If I understand what you're looking for, using jQuery combined with an easy to parse response framework like JSON would probably be what you're looking for. If you're using PHP you can pretty easily create a large nested array of form data and just print it out as JSON like this:
<?php
$form1Array = array(
array('type'=>'text','name'=>'input_a','id'=>'input_a','value'=>'Example default value','label'=>'Input A'),
array('type'=>'text','name'=>'input_b','id'=>'input_b','label'=>'Input B'),
array('type'=>'password','name'=>'input_c','id'=>'input_c','label'=>'Input C'));
$form2Array = array(
array('type'=>'text','name'=>'input_d','id'=>'input_d','label'=>'Input D'),
array('type'=>'text','name'=>'input_e','id'=>'input_e', 'label'=>'Input E'),
array('type'=>'password','name'=>'input_f','id'=>'input_f', 'label'=>'Input F','can_replicate'=>true));
$output = array('form_1'=>array('id'=>'form_1','label'=>'Form 1','elements'=>$form1Array,'can_replicate'=>true),'form_2'=>array('id'=>'form_2','label'=>'Second form','elements'=>$form1Array));
echo json_encode($output,JSON_PRETTY_PRINT);
?>
That will obviously need to be modified to actually construct the structure from your database output, but that should be a fairly simple process. The output of that example will give you a nice JSON output that you can parse using jQuery:
{
"form_1": {
"id": "form_1",
"label": "Form 1",
"elements": [
{
"type": "text",
"name": "input_a",
"id": "input_a",
"value": "Example default value",
"label": "Input A"
},
{
"type": "text",
"name": "input_b",
"id": "input_b",
"label": "Input B"
},
{
"type": "password",
"name": "input_c",
"id": "input_c",
"label": "Input C"
}
],
"can_replicate": true
}
}
After that, you'd just query your php page and assemble the output information into forms and elements:
<!DOCTYPE html5>
<html>
<head>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){ //wait for the whole document to load before modifying the DOM
$.ajax({
url: "test.php",
success: function(data, status, jqXHR) {
var formData = jQuery.parseJSON(data);
$.each(formData, function(id, formInfo){ // function(key, value); In the PHP example, I used the key as the form ID, with the value containing the array of form data
var newForm = $('<form></form>'); //Create an empty form element
newForm.attr('id',formInfo.id); //Set the form ID
$.each(formInfo.elements,function(key, formInput){ //I didn't place a key on the inputs, so the key isn't used here
var newInput = $('<input></input>'); //Create a new empty input
newInput.attr({'id':formInput.id,'type':formInput.type,'name':formInput.name});
if(formInput.value != null){ //If a default value is given
newInput.val(formInput.value); //Set that value
}
if(formInput.label != null){
var newLabel = $('<label></label>');
newLabel.attr('for',formInput.label); //Set the ID that this label is for
newLabel.html(formInput.label); //Set the label text
newForm.append(newLabel);
}
newForm.append(newInput);
if(formInput.can_replicate != null && formInput.can_replicate == true){
//Place code here to insert a button that can replicate the form element
//I'm just going to put a plus sign here
newForm.append('+');
}
newForm.append('<br />');
});
if(formInfo.can_replicate != null && formInfo.can_replicate == true){
//put code here to insert a button that replicates this form's structure
}
if(formInfo.label != null){
var newLabel = $('<label></label>');
newLabel.attr('for',formInfo.id);
newLabel.html(formInfo.label);
$('#form_container').append(newLabel);
}
$('#form_container').append(newForm);
});
}
})
});
</script>
</head>
<body>
<h1>Testing</h1>
<div id="form_container">
</div>
</body>
</html>
After all of that, this is the output the example should produce:
I would recommend checking KnockoutJS. With this Jquery plugin you can dynamically add or remove to you DOM using ko if: comments. Also, you can simply show or hide elements by using the visible data-bind. I would recommend checking out their tutorials the collections sections should give you a good start on how to implement what you are trying to.

MVC dropdownlist onchange call jquery

I have a SelectList representing a delivery type for an order.
The delivery type reference data has the usual code/description, but also an additional boolean property which indicates if further information needs to be entered for the type selected.
So for Emergency deliveries additional data is required. The additional data entry fields would be set visible if Emergency was selected, otherwise hidden
My ViewModel contains <List>ReferenceDeliveryTypes which contains the 3 properties.
I have created a SelectListItems from the ViewModel data
#Html.DropDownListFor(model => model.DeliveryTypeCode,
new SelectList(Model.ReferenceDeliveryTypes as System.Collections.IEnumerable,
"DeliveryTypeCode", "DeliveryTypeDescription"), new { id = "ddlDeliveryType" })
How can I call a jQuery function on change of the delivery type, pass the selected code and check the Model.ReferenceDeliveryTypes for that code to see if the additional data property is true/false to show/hide the additional fields div?
I have managed to get the jQuery function called to pass the value.
$(function () {
$('#ddlDeliveryType').change(function () {
var value = $(this).val();
alert(value);
});
});
I don't know of any way you can do this using a select list but I suggest the following options:
Simple but a hack - add a string to the end of DeliveryTypeDescription, for example (emergency delivery) and check for that in your change function
Another hack - multiply DeliveryTypeCode by 10 and add 1 on if it's an emergency delivery (and then use mod 10 in your change function)
Use an Ajax lookup function
Load a JavaScript lookup table with the codes which require an emergency delivery
Use a hidden field in your form which contains a string list of the emergency codes with a suitable separator
Good luck
UPDATE
For the hidden field option if you use something like 123|456|789| and then use indexOf having appended a | to the selected ID.
I converted the Model.ReferenceDeliveryTypes to a JSON list which allowed me to access it from the jQuery.
Possibly not the best way, but it allows me to do everything on the client rather than making an AJAX call back. I can now show/hide the inside the if block.
Thought it worth documenting what I did as I've not come across the #Html.Raw(Json.Encode before and it might prove useful for someone who wants to access model data from within jQuery.
Any additional comments welcome.
<script type="text/javascript">
var ReferenceDeliveryTypeJsonList=#Html.Raw(Json.Encode(Model.ReferenceDeliveryTypes))
</script>
#Html.DropDownListFor(model => model.DeliveryTypeCode,
new SelectList(Model.ReferenceDeliveryTypes.ReferenceDeliveryType as System.Collections.IEnumerable,
"DeliveryTypeCode", "DeliveryTypeDescription"), new { id = "ddlDeliveryType" })
$(function () {
$('#ddlDeliveryType').change(function () {
var selectedDT= $(this).val();
$.each(ReferenceDeliveryTypeJsonList, function (index, item) {
if (selectedDT === item.DeliveryTypeCode) {
alert("match " + selectedDT);
}
});
});
});

Categories