Invisible Google Recapthca two in Sweetalert2 - javascript

I saw the code snippets at Google reCaptcha within SweetAlert modal window , particularly the one without the jquery (the 1st answer)
But that solution makes the captcha box visible.
How do i make it invisible within Sweetalert? I will do a server backend validation
I looked at https://github.com/prathameshsawant7/multiple-invisible-recaptcha and included the entire code from init_recaptcha.js along with the "onOpen" feature of SWAL... but no luck. The captcha is not set in the form.
Any help will be appreciated> I need the invisible captcha as I will triggering several SWAL forms within the HTML tag of the sweetalert (needed for multiple inputs at time)
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/cover.css">
<script src="js/jquery1.11.1.min.js" type="text/javascript"></script>
<script src="js/sweetalert2#10.js"></script>
<script>
$(document).ready(function() {
$(document).on('click', '#comment_button', function(e) {
e.preventDefault();
//var M_user = "";
Swal.fire({
title: 'Add a comment to the page',
titleText: 'Add a comment to the page',
//included dynamically... :-)
width: '75%',
position: 'center',
showCancelButton: true,
showConfirmButton: false,
showCloseButton: true;
allowOutsideClick: false,
focusConfirm: false,
//grow: 'fullscreen',
allowEscapeKey: true,
html: '<div class="card"><form name="form2" id="form2" method="post" action="process3.php"><div class="form-group"><label for="InputText2">Enter Text (Recaptcha 2)</label><input type="text" class="form-control" id="text2" name="textmsg" placeholder="Enter random text" value="this_is_textmsg"></div><div class="form-group"><label for="InputText3">Enter Text :</label><input type="text" class="form-control" id="textv3" name="textmsg3" placeholder="Enter random text" value="this_is_textmsg3"></div><div id="recaptcha-form-2" style="display:none;"></div><input type="button" class="btn btn-primary" onclick="formSubmit(\'2\')" value="Submit" /></form></div>',
footer: 'We reserve the right to moderate your comments if we feel the need.',
onOpen: function() {
console.log('setting initial values, if any on onOpen');
/***
* #Created by: Prathamesh Sawant (prathameshsandeepsawant#gmail.com)
* #Date : 24/07/2017
* #description To handle Multiple Google Invisible reCaptcha Implementation
*/
/**************************************************************************\
* Step 1 - Initialize reCaptcha Site Key and Widget eg: widget_1 for Form 1
* Step 2 - In init function add code to create form submit callback action.
* Step 3 - Call renderInvisibleReCaptcha function by passing reCaptcha ID
* and createCallbackFn Response.
***************************************************************************/
"use strict";
var PS = PS || {};
var widget_1;
var widget_2;
var widget_3;
var recaptcha_site_key = 'my Real Site Key Goes here....removed for stackoverflow';
if (typeof PS.RECAPTCHA === 'undefined') {
(function(a, $) {
var retryTime = 300;
var x = {
init: function() {
if (typeof grecaptcha != 'undefined') {
//For Form 1 Initialization
if ($('#form1 #recaptcha-form-1').length > 0) {
var callbackFn = {
action: function() {
saveData('1'); //Here Callback Function
}
}
/*--- 'recaptcha-form-1' - reCaptcha div ID | 'form1' - Form ID ---*/
widget_1 = x.renderInvisibleReCaptcha('recaptcha-form-1', x.createCallbackFn(widget_1, 'form1', callbackFn));
}
//For Form 2 Initialization
if ($('#form2 #recaptcha-form-2').length > 0) {
var callbackFn = {
action: function() {
saveData('2'); //Here Callback Function
}
}
/*--- 'recaptcha-form-2' - reCaptcha div ID | 'form2' - Form ID ---*/
console.log('defining widget 2');
widget_2 = x.renderInvisibleReCaptcha('recaptcha-form-2', x.createCallbackFn(widget_2, 'form2', callbackFn));
}
//For Form 3 Initialization
if ($('#form3 #recaptcha-form-3').length > 0) {
var callbackFn = {
action: function() {
saveData('3'); //Here Callback Function
}
}
/*--- 'recaptcha-form-3' - reCaptcha div ID | 'form3' - Form ID ---*/
widget_3 = x.renderInvisibleReCaptcha('recaptcha-form-3', x.createCallbackFn(widget_3, 'form3', callbackFn));
}
} else {
setTimeout(function() {
x.init();
}, retryTime);
}
},
renderInvisibleReCaptcha: function(recaptchaID, callbackFunction) {
return grecaptcha.render(recaptchaID, {
'sitekey': recaptcha_site_key,
"theme": "light",
'size': 'invisible',
'badge': 'inline',
'callback': callbackFunction
});
},
createCallbackFn: function(widget, formID, callbackFn) {
return function(token) {
$('#' + formID + ' .g-recaptcha-response').val(token);
if ($.trim(token) == '') {
grecaptcha.reset(widget);
} else {
callbackFn.action();
}
}
}
}
a.RECAPTCHA = x;
})(PS, $);
}
$(window).load(function() {
PS.RECAPTCHA.init();
});
},
preConfirm: function(login) {
//run any stuff to do before login here
console.log('doing before preConfirm stuff....');
//let s_user_name = Swal.getPopup().querySelector('#s_user_name').value;
//M_user= s_user_name;
},
}).then(function(result) {
console.log('then function result initial');
if (result.value) {
console.log('Doing if result.value here...');
}
}) //swal.fire end
}); //function(e) ends
}); //doc ready ends
</script>
</head>
<body>
<div class="site-wrapper">
<div class="site-wrapper-inner">
<div class="cover-container">
<div class="masthead clearfix">
<div class="inner">
<center>
<h3 class="">Handle Multiple Invisible Recaptcha</h3>
</center>
</div>
</div>
<div class="inner cover">
<hr>
<form id="comment_form" name="comment_form_name"><button id="comment_button" class="coach_button">Leave a Comment...</button></form>
</hr>
<div class="mastfoot">
<div class="inner">
<p>Sample template by #Prathamesh-Sawant to implement multiple invisible recaptcha on single page dynamically.</p>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?render=explicit"></script>
<script src="js/form.js" async defer></script>
</body>
</html>

I figured it out
Basically, inside sweetalert2, captcha doesn't render...
I was anyway using multiple fields in SWAL... so I had to use fields inside the HTML parameter.
To address the invisible captcha, I took an idea from Invisible reCaptcha AJAX Call . From within sweetalert, using Swal.getPopup().querySelector('#field').value, in pre-confirm, I will populate hidden fields in the form that is outside of sweetalert and submit it via Ajax
Not a brilliantly clean solution but, hey, it works!

Related

Add like on click

I need to create a cookie method with the current time, which will first check the data (like_finger and article_id), and if there is no data, then add a like and update the date, if there is data, then do nothing.
I have a function
$likes = request()->cookie('like_finger');
$hours = 24;
if ($likes) {
Article::find($id)
->where('updated_at', '<', Carbon::now()->subHours($hours)->toDateTimeString())
->increment('like_finger');
}
But I can't check it yet, because I got confused in the add like button
I added a button to php.blade and created a function in js
<input type="button" id="start" value="Like Finger" onclick="startCombine(this)"> {{ $article->like_finger }}
function startCombine(startButton) {
startButton.disabled = true;
startButton.disabled = false;
}
How can I make sure that a like is added when true?
I want that when the button is clicked, one like is added, which will be stored in cookies for 24 hours, I wrote an approximate function of how the like should be added, but it is not perfect, since there is no button functionality
First of all, you should never store any logic in the client side. A great alternative for this kind of feature would be using the Laravel Aquantances package.
https://laravel-news.com/manage-friendships-likes-and-more-with-the-acquaintances-laravel-package
Anyway, since you want to do it with cookies;
We can actually do this a lot easier than thought.
Articles.php
class User extends Authenticatable
{
// ...
public static function hasLikedToday($articleId, string $type)
{
$articleLikesJson = \Cookie::get('article_likes', '{}');
$articleLikes = json_decode($articleLikesJson, true);
// Check if there are any likes for this article
if (! array_key_exists($articleId, $articleLikes)) {
return false;
}
// Check if there are any likes with the given type
if (! array_key_exists($type, $articleLikes[$articleId])) {
return false;
}
$likeDatetime = Carbon::createFromFormat('Y-m-d H:i:s', $articleLikes[$articleId][$type]);
return ! $likeDatetime->addDay()->lt(now());
}
public static function setLikeCookie($articleId, string $type)
{
// Initialize the cookie default
$articleLikesJson = \Cookie::get('article_likes', '[]');
$articleLikes = json_decode($articleLikesJson, true);
// Update the selected articles type
$articleLikes[$articleId][$type] = today()->format('Y-m-d H:i:s');
$articleLikesJson = json_encode($articleLikes);
return cookie()->forever('article_likes', $articleLikesJson);
}
}
The code above will allow us to is a user has liked an article and generate the cookie we want to set.
There are a couple important things you need to care about.
Not forgetting to send the cookie with the response.
Redirecting back to a page so that the cookies take effect.
I've made a very small example:
routes/web.php
Route::get('/test', function () {
$articleLikesJson = \Cookie::get('article_likes', '{}');
return view('test')->with([
'articleLikesJson' => $articleLikesJson,
]);
});
Route::post('/test', function () {
if ($like = request('like')) {
$articleId = request('article_id');
if (User::hasLikedToday($articleId, $like)) {
return response()
->json([
'message' => 'You have already liked the Article #'.$articleId.' with '.$like.'.',
]);
}
$cookie = User::setLikeCookie($articleId, $like);
return response()
->json([
'message' => 'Liked the Article #'.$articleId.' with '.$like.'.',
'cookie_json' => $cookie->getValue(),
])
->withCookie($cookie);
}
});
resources/views/test.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<meta name="csrf-token" content="{{ csrf_token() }}" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
</head>
<body>
<div class="container">
#if (session('success'))
<div class="alert alert-success" role="alert">
{{ session('success') }}
</div>
#endif
<pre id="cookie-json">{{ $articleLikesJson }}</pre>
<div class="row">
#foreach (range(1, 4) as $i)
<div class="col-3">
<div class="card">
<div class="card-header">
Article #{{ $i }}
</div>
<div class="card-body">
<h5 class="card-title">Special title treatment</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="/test?like=heart&article_id={{ $i }}" class="btn btn-primary like-button">
Like Heart
</a>
<a href="/test?like=finger&article_id={{ $i }}" class="btn btn-primary like-button">
Like Finger
</a>
</div>
<div class="card-footer text-muted">
2 days ago
</div>
</div>
</div>
#endforeach
</div>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#4.6.0/dist/js/bootstrap.min.js" integrity="sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF" crossorigin="anonymous"></script>
<script>
$(function() {
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$('.like-button').on('click', function(event) {
event.preventDefault();
let href = $(this).attr('href');
$.ajax({
url: href,
type: 'POST',
success: function (response) {
alert(response.message)
$('#cookie-json').text(response.cookie_json)
}
})
});
});
</script>
</body>
</html>
In Blade you can have conditionals, like
#if (count($records) === 1)
I have one record!
#elseif (count($records) > 1)
I have multiple records!
#else
I don't have any records!
#endif
Example taken from https://laravel.com/docs/8.x/blade
Let's try to apply it on your specific issue
#if ($article->article_id && $article->like_finger)
<input type="button" id="start" value="Like Finger" onclick="startCombine(this)"> {{ $article->like_finger }}
#endif
Make an AJAX request in startCombine() when the button is clicked.
Your server code processing the like seems to use a cookie named like_finger, so before the request to the server is made you create that cookie:
const d = new Date();
d.setTime(d.getTime() + (24*60*60*1000)); // Expiry in milliseconds
document.cookie = "like_finger=" + d.toUTCString() +
";expires=" + d.toUTCString() +
";path=/";
(The cookie is set to expire after 24 h with the above values)
Then you want to send that to the server:
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "the_place_where_php_processing_code_is.php", true);
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.onreadystatechange = function() { // When status of the ongoing request changes
if (this.readyState == 4 && this.status == 200) {
// Server has processed the like request and is done
// You can do some "summary" here, like disabling the Like button
var response = this.responseText;
}
};
var data = {
id : 1, // The id of the article being liked, not sure how to retrieve that right now
};
xhttp.send(JSON.stringify(data));
..and then you use something like this in the receiving PHP code on the server:
$id = $request->input('id'); // $id = 1 (in this case, of course)

how to save checked checkboxes after reloading data using ajax call

I defined a dropbox selector which after selecting a country gives me a list of cities as checkboxs. I am using jquery and ajax to preview it and select it as follow.
<div>
<div id="preview-items">
</div>
</div>
<script>
$("#id_country").change(function () {
var countryId = $(this).val(); // get the selected country ID from the HTML input
$.ajax({ // initialize an AJAX request
url: '/ajax/ajax_load_cities',
data: {
'countries': countryId // add the country id to the GET parameters
},
dataType: 'json',
success: function (data) { // `data` is the return of the `load_cities` view function
$('#preview-items').html('');
for (var i in data.tags) {
$('#preview-items').append(`
<label class="btn btn-primary mr-1">
<input type="checkbox" id="checklist_` + data.tags[i][0]+ `" value="` + data.tags[i][0] + `">
<span>
` + data.tags[i][1] + `
</span>
</label><br />`
).find("#checklist_" + data.tags[i][0]).on("change", function(e){
var cities_array = $(":checkbox:checked").map( function() { return $(this).next().html(); }).get();
$("#preview-items").html(cities_array.join(', '));
if (e.target.checked) {
localStorage.checked = true;
} else {
localStorage.checked = false;
}
}
}
});
});
</script>
and in django view.py:
def load_cities(request):
....
data = {
'tags': list(cities)
}
return JsonResponse(data)
The problem is that, it does not keep the selected cities after changing the country selectors. after googling, I found that cookies are a good choice. I wanted to know how to save selected checkboxes when dropbox items change?
I don't think you really need cookies or localstorage. I suggest you take another approach:
first you create the javascript code that builds the dropbox selector based on a state, in your example the list of cities.
then when you make the ajax call you just call that function again.
window.onload = function() {
var $container = $('#preview-items')
// when starting or loading the page I suggest either outputting the initial list
// of unchecked data in your markup somewhere as a json object
// or perform an initial ajax call. Here I just store it in a local variable.
var initialData = [
{id: 1, name: "Amsterdam", checked: false},
{id: 2, name: "Berlin", checked: true},
{id: 3, name: "Brussels", checked: false},
]
buildDropbox(initialData)
function buildDropbox(cities) {
$container.empty()
cities.forEach(function(city) {
let checked = city.checked? "checked=\"checked\"" : ""
$container.append(`
<label class="btn btn-primary mr-1" for="checklist_${city.id}">
<input type="checkbox" ${checked} id="checklist_${city.id}" value="${city.id}">
<span>${city.name}</span>
</label><br />`
)
var chk = document.getElementById("checklist_" + city.id)
chk.addEventListener("change", function(e) {
saveChange(e.currentTarget.name, e.currentTarget.value, e.currentTarget.checked)
})
})
}
function saveChange(chkName, chkValue, chkChecked) {
console.log("POST ajax for " + chkValue + " with checked: " + chkChecked);
// do your ajax call here, and in the success
// be sure to return the same list as the
// initialData, but with the updated 'checked'
// value, obviously.
// here, for testing purposes, I reuse the initialData
// and check all checkboxes randomly
let updatedData = initialData.map(function(c) {
return {...c, checked: Math.round(Math.random()) == 0}
})
buildDropbox(updatedData)
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<div id="preview-items"></div>
</body>
</html>
Here's an example. It may not work here due to restrictions, but you can set it up to work for you.
It saves every 5 seconds and checks if its true, and if it is, save it.
var check = document.getElementById("checkbox1");
function change() {
window.setTimeout(function() {
change();
if (check.checked == true) {
localStorage.setItem("isChecked", check.checked);
}
}, 5000);
}
change();
window.onload = function() {
check.checked = localStorage.getItem("isChecked")
}
h1 {
font-family: "Comic Sans MS", sans-serif;
}
<h1>Click the check box</h1>
<br><br>
<input type="checkbox" id="checkbox1"> <!--Your Checkbox-->
I hope it works for your app!

Google recaptcha callback not working with multiple reCAPTCHA

I have multiple google captchas on page. Code:
<script>
var qqqq =function(captcha_response){
console.log('?')
}
var CaptchaCallback = function(){
$('.g-recaptcha').each(function(index, el) {
grecaptcha.render(el, {'sitekey' : '{{ recaptcha_key}}', 'callback': 'qqqq', 'theme': 'dark'});
});
};
</script>
<script src='https://www.google.com/recaptcha/api.js?onload=CaptchaCallback&render=explicit' async defer></script>
On the page there are several blocks for reCAPTCHA:
<div class="g-recaptcha"></div>
All reCAPTCHA's render well, all with dark theme, all do verifying work, but the callback function does not get called.
When I tried single reCAPTCHA with data-callback attribute, it worked well.
I was facing the same issue. After checking the documentation again I found my problem. Try to remove the single quotation marks around your function name.
Like this:
<script>
var qqqq =function(captcha_response){
console.log('?')
}
var CaptchaCallback = function(){
$('.g-recaptcha').each(function(index, el) {
grecaptcha.render(el, {'sitekey' : '{{ recaptcha_key}}', 'callback': qqqq });
});
};
</script>
Maybe this helps someone else as well :)
Steps to implement multimple recaptcha with a callback method for disable the submit button
1) Add the reference
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
2) Add the Code that will render the captcha widget
<script>
var CaptchaCallback = function () {
grecaptcha.render('RecaptchaField1', { 'sitekey': 'xxx', callback: 'recaptchaCallback1'});
grecaptcha.render('RecaptchaField2', { 'sitekey': 'xxx', callback: 'recaptchaCallback2' });
grecaptcha.render('RecaptchaField3', { 'sitekey': 'xxx', callback: 'recaptchaCallback3' });
};
</script>
3) Add the method to remove the disable property on the submit button
$('#GetHelpButton').prop('disabled', true);
function recaptchaCallback1()
{
$('#GetHelpButton').prop('disabled', false);
}
4) Add the widget inside a form
<form id="formContact" method="post" class="login-form margin-clear" action="Landing/SendContactForm">
<div class="form-group has-feedback">
<label class="control-label">Correo electronico</label>
<input name="EmailContact" value="" id="EmailContact" type="text" class="form-control" placeholder="">
<i class="fa fa-envelope form-control-feedback"></i>
</div>
<div id="RecaptchaField1"></div>
<button id="GetHelpButton" type="submit" data-sitekey="xxxx" class="btn btn-info col-md-12 g-recaptcha">Send</button>
Try setting a unique ID for each div and then use a for loop to iterate through them. Something like this:
window.onloadCallback = function() {
var captcha = ['recaptcha1' ,'recaptcha2', 'recaptcha3']; //put your IDs here
for(var x = 0; x < captcha.length; x++){
if($('#'+captcha[x]).length){
grecaptcha.render(captcha[x], {
'sitekey' : '{{ recaptcha_key}}',
'theme' : 'dark',
'callback': function() {
console.log("Woof");
}
});
}
}
};

ReferenceError: obj is not defined using webcomponents & polymer

This is my first project using web components and I'm a rookie at best with js/jquery, so I am not sure why this happening.
I have a custom element built in "search-form.html", all of my components and scripts are then brought in via a master "components.html" in the head of my index as such...
index.html:
<head>
<meta charset='utf-8'>
<script src="/static/template/js/webcomponents.min.js"></script>
<link rel="import" href="/static/template/components/components.html">
<link rel="icon" type="image/png" href="/static/template/img/favicon.png">
</head>
components.html:
<!-- POLYMER MUST BE LOADED -->
<link rel="import" href="/static/template/polymer/polymer.html">
<!-- TEMPLATE SCRIPTS -->
<link rel="import" href="/static/template/components/scripts.html">
<!-- Loads jquery and other scripts -->
<!-- TEMPLATE COMPONENTS -->
<link rel="import" href="/static/template/components/search-form.html">
then the search-form.html looks like this:
<!-- Defines element markup -->
<dom-module id="search-form">
<template>
<div class="input-prepend input-append">
<form id="search" method="get" action="/property/search">
<div class="btn-group">
<button id="search-type" class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
Search by
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a data-name="city" data-label="city">City</a></li>
<li><a data-name="zip" data-label="zip">Zip Code</a></li>
<li><a data-name="mls" data-label="mls">MLS Number</a></li>
</ul>
</div>
<input id="input-tag" class="input-xlarge" type="text" placeholder="Select a search type..." data-provide="typeahead" value="" />
<button type="submit" class="btn"><i class="fa fa-search"></i></button>
</form>
</div>
</template>
<script>
/********************************
/ TYPEAHEAD ARRAY
/*******************************/
var obj = {
"city" : [],
"zip" : [],
};
$(document).ready(function() {
/*dynamic zipcode*/
for(i=43000; i<=45999;i++){
obj.zip.push({val: i.toString(), string: i.toString()});
}
for(i=48000; i<=49999;i++){
obj.zip.push({val: i.toString(), string: i.toString()});
}
});
/********************************
/ SEARCH TYPEAHEAD FUNCTION
/*******************************/
$(function searchTag($) {
var data = [];
$('.dropdown-menu > li > a').on("click", function() {
data = $(this).data('name');
});
var that = this;
$('#input-tag').typeahead({
source: function(query, process) {
var results = _.map(obj[data], function(value) {
return value.val;
});
process(results);
},
highlighter: function(val) {
var value = _.find(obj[data], function(p) {
return p.val == val;
});
return value.string;
},
updater: function(val) {
var value = _.find(obj[data], function(p) {
return p.val == val;
});
that.setSelected(value);
return value.string;
}
});
this.setSelected = function(value) {
$('#input-tag').val(value.string);
//$('#input-tag').attr('data-value', value.val);
$('#input-tag').data('value', value.val);
};
});
/********************************
/ QUICK SEARCH TAG FUNCTION
/*******************************/
$(function () {
var caret = ' <span class="caret"></span>';
function searchSelect() {
$('.dropdown-menu > li > a').on("click", function() {
$('#search-type').html($(this).text() + caret);
$('#input-tag').attr('placeholder', $(this).data('label'));
$('#input-tag').attr('name', $(this).data('label'));
});
}searchSelect();
});
Polymer({
is: 'search-form',
created: function() {},
ready: function() {},
attached: function() {},
detached: function() {},
attributeChanged: function(name, type) {}
});
</script>
</dom-module>
so the var obj is declared in search-form.html
finally, because of the way our back end is written, I have to run a loop on index.html to get the array to be used in var obj for "city" : [],
that looks like this:
/*TYPEAHEAD ARRAY*/
//MUST BE RUN FROM SHELL BECAUSE OF THE TMPL LOOP
<!-- TMPL_LOOP Cities -->
obj.city.push({val: "<!-- TMPL_VAR city_name -->", string: "<!-- TMPL_VAR city_name -->"})
<!-- /TMPL_LOOP -->
So now the problem. This all works without error in chrome, but I get the same error in FF, IE11, and Edge. That error is:
ReferenceError: obj is not defined
obj.city.push({val: "ALLEN PARK", string: "ALLEN PARK"})
Man I hope I documented this well enough for someone to help, because I have been pulling my hair out for hours before turning to stack :)
I think the imports are not loaded yet when you access the obj var in index.html, thus obj is undefined at this time. Since Chrome supports HTML imports natively, the imports are loaded earlier and it works there. Wrap the access to obj in HTMLImports.whenReady(callback). The callback will be called when all HTML imports have finished loading.
HTMLImports.whenReady(function(){
<!-- TMPL_LOOP Cities -->
obj.city.push({val: "<!-- TMPL_VAR city_name -->", string: "<!-- TMPL_VAR city_name -->"})
<!-- /TMPL_LOOP -->
});

Knockout bindings not working as expected for manipulating observable array

We have a view using Razor and Knockout.js that displays a form. Part of the form asks the user to enter a list of values, and we're using a ko.observablearray to keep track of them. This list is represented as a bunch of text boxes, one per value, with a "Delete" button next to each box and a single "Add" button underneath all of them. It works similarly to the demo project at http://learn.knockoutjs.com/#/?tutorial=collections.
Our form is acting unexpectedly in two ways:
When a delete button is clicked, it removes all values from the ko.observablearray, not just the one corresponding to what was clicked.
When the "Submit" button for the overall form is clicked, it adds a new element to the ko.observablearray instead of submitting the form to our server.
Why are we seeing this behavior? (I know that these are two separate issues, but I'm not sure if they're caused by the same underlying problem or not, which is why I'm posting them in one question.)
Here is our Razor view:
#model OurProject.Models.Input.InputModel
#{
ViewBag.Title = "Input";
}
<h2>Inputs</h2>
<div id="inputForm">
<!-- snip - lots of input elements to fill in that are bound to KO -->
<div>
#Html.LabelFor(model => model.POSTransactionCodes)
</div>
<div>
<span class="help-block">Separate values by commas.</span>
</div>
<div>
<ul data-bind="foreach: POSTransactionCodes">
<li><input data-bind="value: $data" /> Delete</li>
</ul>
<button data-bind="click: addPOSTransactionCode">Add another POS Transaction Code</button>
#Html.ValidationMessageFor(model => model.POSTransactionCodes, null, new { #class = "help-inline" })
</div>
<!-- snip - more input elements -->
<button data-bind="click: save">Submit</button>
</div>
<script type="text/javascript" src='~/Scripts/jquery-1.8.2.min.js'></script>
<script type="text/javascript" src='~/Scripts/knockout-2.1.0.js'></script>
<script type="text/javascript" src='~/Scripts/OP/OP.js'></script>
<script type="text/javascript" src='~/Scripts/OP/Input/OP.Input.Input.Form.js'></script>
<script type="text/javascript" src='~/Scripts/OP/Input/OP.Input.Input.Data.js'></script>
<script type="text/javascript">
var elementToBindTo = $("#inputForm")[0];
OP.Input.Input.Form.init(elementToBindTo);
</script>
Here is our main piece of Knockout code, OP.Input.Input.Form.js:
extend(OP, 'OP.Input.Input.Form');
OP.Input.Input.Form = function (jQuery) {
//The ViewModel for the page
var ViewModel = function () {
var self = this;
//Fields
/* snip - lots of ko.observables() */
self.POSTransactionCodes = ko.observableArray([]); //is a list of transaction codes
/* snip - lots of ko.observables() */
//Set up with initial data
self.initialize = function () {
var c = function (data, status, response) {
if (status === "success") {
/* snip - lots of ko.observables() */
ko.utils.arrayPushAll(self.POSTransactionCodes, data.POSTransactionCodes);
self.POSTransactionCodes.valueHasMutated();
/* snip - lots of ko.observables() */
} else {
}
};
OP.Input.Input.Data.GetInput(c);
}
//When saving, submit data to server
self.save = function (model) {
var c = function (data, status, response) {
if (status === "success") {
//After succesfully submitting input data, go to /Input/Submitted
//in order to let MVC determine where to send the user next
window.location.href = "~/Input/Submitted";
} else {
}
};
OP.Input.Input.Data.SaveInput(model, c);
}
//Modifying POSTransactionCodes array
self.removePOSTransactionCode = function (POScode) {
self.POSTransactionCodes.remove(POScode)
}
self.addPOSTransactionCode = function () {
self.POSTransactionCodes.push("");
}
};
//Connect KO form to HTML
return {
init: function (elToBind) {
var model = new ViewModel();
ko.applyBindings(model, elToBind);
model.initialize();
}
};
} ($);
Here is OP.Input.Input.Data.js:
extend(OP, 'OP.Input.Input.Data');
OP.Input.Input.Data = {
GetInput: function (callback) {
$.get("/API/Input/InputAPI/GetInputModel", callback);
},
SaveInput: function (input, callback) {
$.ajax({
url: "/API/Input/InputAPI/SaveInput",
type: "post",
data: input,
complete: callback
});
}
};
You need to be pushing a new ViewModel into your observable array. Which will contain observable properties.
So to do this I created a new view model called TransactionCodeView
var TransactionCodeView = function() {
var self = this;
self.code = ko.observable("");
};
Then when the user clicks "Add another POS Transaction Code":
self.addPOSTransactionCode = function () {
self.POSTransactionCodes.push(new TransactionCodeView());
}
The only other thing changed was in the HTML binding:
<li><input data-bind="value: code" /> Delete</li>
Because code is the observable property in the new viewmodel we bind the input value to that.
Take a look at this jsfiddle. I haven't tested the submit functionality for obvious reasons ;-)
This is why the submit functionality wasn't working on my form:
In the view, I had this Razor:
<div>
<ul data-bind="foreach: POSTransactionCodes">
<li><input data-bind="value: $data" /> Delete</li>
</ul>
<button data-bind="click: addPOSTransactionCode">Add another POS Transaction Code</button>
#Html.ValidationMessageFor(model => model.POSTransactionCodes, null, new { #class = "help-inline" })
</div>
Using the button element for my "Add" button was causing it to respond to the user pressing enter instead of the submit button at the end of the form. When I changed the button into an input element instead, it started working as expected.
<input type="button" value="Add another POS Transaction Code"
data-bind="click: addPOSTransactionCode" />

Categories