I have a wordpress site using angular to handle ajaxed page loads. When loading a page directly, the gravity forms load as expected. If you load from a non grav forms page then navigate to one with a form, you get nothing.
This may have something to do with the scripts grav forms embeds below the forms. So I am wondering if there is a way to init a form after being loaded in via ajax.
I can use some html, jquery magic to fetch the form via ajax after page load using this in functions.php:
gravity_form_enqueue_scripts( $form_id, true );
echo gravity_form($form_id, false, false, false, null, true, 12, true);
When I inject the results into the DOM I just get this console error:
gformInitSpinner is not defined
and even if I target the new html and show the form, which comes through as html, I get a massively malformed form that doesn't work. Clearly because the scripts don't get evaluated and that js error blocks it anyhow.
So does anyone know if there is a way to make gravity forms work with an asynchronous site? Or if you can load a functional form using ajax?
I am not able to fill the enqueue system with gravity_form_enqueue_scripts since there could be any number of forms made at any time. That's not a practical solution.
Well, no thanks to the gravity forms development team and the entire internet, we have a solution for all of us tasked with what seems like an obvious use of a very commonly used plugin...
First off, set up your functions.php file so it can handle this:
// filter the Gravity Forms button type
add_filter( 'gform_submit_button', 'form_submit_button', 10, 2 );
function form_submit_button( $button, $form ) {
return "<button class='btn btn-green' type='submit' id='gform_submit_button_{$form['id']}'>Submit</button>";
}
// Hook up the AJAX actions
add_action( 'wp_ajax_nopriv_gf_button_get_form', 'gform_ajax_get_form' );
add_action( 'wp_ajax_gf_button_get_form', 'gform_ajax_get_form' );
// Add the "button" action to the gravityforms shortcode
// e.g. [gravityforms action="button" id=1 text="button text"]
add_filter( 'gform_shortcode_form', 'gform_shortcode', 10, 3 );
function gform_shortcode( $shortcode_string, $attributes, $content )
{
$a = shortcode_atts( array(
'id' => 0,
'text' => 'Show me the form!',
), $attributes );
$form_id = absint( $a['id'] );
if ( $form_id < 1 ) {
return 'Missing the ID attribute.';
}
$ajax_url = admin_url( 'admin-ajax.php' );
$html = sprintf('<div ng-controller="GavityFormController as garvityFormCtrl" ng-init="init(\'%s\', \'%s\')"><div class="gform_container" ng-include="form"></div></div>', $form_id, $ajax_url);
return $html;
}
function gform_ajax_get_form(){
$form_id = isset( $_GET['form_id'] ) ? absint( $_GET['form_id'] ) : 0;
$form = gravity_form($form_id, false, false, false, false, true, 0, false);
$form = str_replace('text/javascript', 'text/ng-javascript', $form);
$form = str_replace('<script ', '<script lazy-js ', $form);
echo $form;
die();
}
add_filter("wp_footer", "init_gf_scripts");
function init_gf_scripts() {
global $wpdb;
//obviously change this if your prefix is different
$sql = "SELECT `id` FROM `wp_rg_form` WHERE `is_active` = 1 AND `is_trash` = 0";
$res = $wpdb->get_results($sql);
foreach($res AS $r)
{
gravity_form_enqueue_scripts( $r->id, true );
}
}
Next step can vary depending on how you set up your main js file. The necessary points being below. There's usually quite a bit more but hopefully you get the idea.
(function(){
require('./directives/forms/form');
var util = require('./util');
var app = angular.module('yourapp', [
'yourapp-form',
])...
util.js ... probably a common file. I'm not sure if it really has anything to do with the actual problem but it is required by the form components so here you go:
module.exports = (function() {
var api = {
isMobileOrSmaller: function () {
return $(window).width() < 681
},
findByProp: function (data, value, prop) {
prop = prop || 'id';
return $.grep(data, function(e){ return e[prop] == value; });
},
getIndexOf: function (collection, value, prop) {
prop = prop || 'id';
return collection.map(function (e) { return e[prop] }).indexOf(value);
},
roundTo: function (value, to) {
to = to || 10;
return Math.floor(value/to) * to;
},
debounce: function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
},
getScrollbarWidth: function () {
return $(window).width();// - $('#siteBody > div').width();
},
getRandomInt: function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
};
return api;
})();
And the form directives...
/directives/forms/form.js
module.exports = (function() {
var api = {};
var form = angular.module('yourapp-form', [
]).directive('lazyJs', [function () {
return {
restricted: 'A',
link: function (scope, element) {
var code = element.text();
var tmpFunc = new Function(code);
scope.$on('GavityFormController::includeContentLoaded', function () {
tmpFunc();
if(window['gformInitDatepicker']) {
gformInitDatepicker();
}
if (window['gformInitPriceFields']) {
gformInitPriceFields();
}
})
}
}
}]).controller('GavityFormController', ['$scope', '$rootScope', '$element', '$http', function ($scope, $rootScope, $element, $http) {
var panel = this,
formId = 0;
$scope.form = '';
$scope.init = function (id, url) {
formId = id;
$scope.form = url + '?action=gf_button_get_form&form_id=' + id
}
$scope.$on('$includeContentLoaded', function(e, src){
if ($('#gform_wrapper_' + formId).length > 0) {
$scope.$broadcast('GavityFormController::includeContentLoaded');
}
});
}]);
//registerring componenets
var radio = require ('./radio');
radio.register(form);
var select = require ('./select');
select.register(form);
var text = require ('./text');
text.register(form);
var check = require ('./check');
check.register(form);
var textarea = require('./textarea');
textarea.register(form);
api.form = form;
return api;
})();
and these are all the required form component files:
radio.js:
module.exports = (function() {
var api = {};
api.register = function (form) {
form.directive('myappRadio', function(){
return {
restricted: 'E',
transclude: true,
replace: true,
scope: {
model: '=model'
},
template: function ($element, $attrs) {
return '<div class="ipt ipt-radio"> \
<div ng-transclude></div> \
<label for="' + $attrs.for + '">' + $attrs.label + '</label> \
</div>';
}
}
});
}
return api;
})();
select.js:
var util = require('./../../util');
module.exports = (function() {
var api = {};
api.register = function (form) {
form.directive('select', ['$timeout', function($timeout){
return {
restricted: 'E',
link: function($scope, $element, $attrs) {
$timeout(function () {
Selectize.define('input_modify', function(options) {
var self = this;
this.setup = (function() {
var original = self.setup;
return function() {
original.apply(this, arguments);
this.$control.find('input').attr('id', '');
};
})();
});
$($element).selectize({
plugins: ['remove_button', 'input_modify']
});
}, 10);
}
}
}])
}
return api;
})();
text.js
module.exports = (function() {
var api = {};
api.register = function (form) {
form.directive('myapptext', function(){
return {
restricted: 'E',
scope: {
model: '=model',
ngrequired: '='
},
link: function(scope, element, attrs, controllers) {
var panel = this;
scope.isActive = scope.model !== '';
scope.required = false;
if (attrs.required === "")
scope.required = true;
if (typeof scope.ngrequired !== "undefined")
scope.required = scope.ngrequired;
scope.onEnter = function () {
scope.isActive = true;
}
scope.onLeave = function () {
if (scope.model == '' || typeof scope.model == 'undefined'){
scope.isActive = false;
}
}
scope.$watch('ngrequired', function(value){
scope.required = scope.ngrequired;
});
},
template: function (element, attr) {
var id = attr.id || 'frm-' + attr.name,
type = attr.type || 'text';
value = attr.value || '';
return '<div class="ipt-text-wrappper" ng-class="{\'m-active\': isActive}"> \
<input ng-required="required" ng-focus="onEnter()" ng-blur="onLeave()" ng-model="model" id="' + id + '" type="' + type + '" name="' + attr.name + '" value="' + value + '" class="ipt ipt-text"> \
<label ng-click="onEnter()" for="' + id + '">' + attr.label + '</label> \
</div>';
}
}
});
}
return api;
})();
check.js:
module.exports = (function() {
var api = {};
api.register = function (form) {
form.directive('myappCheck', function(){
return {
restricted: 'E',
transclude: true,
replace: true,
scope: {
model: '=model'
},
template: function ($element, $attrs) {
return '<div class="ipt ipt-radio"> \
<div ng-transclude></div> \
<label for="' + $attrs.for + '">' + $attrs.label + '</label> \
</div>';
}
}
});
}
return api;
})();
textarea.js:
module.exports = (function() {
var api = {};
api.register = function (form) {
form.directive('myapptextarea', function(){
return {
restricted: 'E',
scope: {
model: '=model',
ngrequired: '='
},
link: function(scope, element, attrs, controllers) {
var panel = this;
scope.isActive = scope.model !== '';
scope.required = false;
if (attrs.required === "")
scope.required = true;
if (typeof scope.ngrequired !== "undefined")
scope.required = scope.ngrequired;
scope.onEnter = function () {
scope.isActive = true;
}
scope.onLeave = function () {
if (scope.model == '' || typeof scope.model == 'undefined'){
scope.isActive = false;
}
}
scope.$watch('ngrequired', function(value){
scope.required = scope.ngrequired;
});
},
template: function (element, attr) {
var id = attr.id || 'frm-' + attr.name,
value = attr.value || '';
return '<div class="ipt-text-wrappper" ng-class="{\'m-active\': isActive}"> \
<textarea ng-required="required" ng-focus="onEnter()" ng-blur="onLeave()" ng-model="model" id="' + id + '" name="' + attr.name + '" class="ipt ipt-textarea">' + value + '</textarea> \
<label ng-click="onEnter()" for="' + id + '">' + attr.label + '</label> \
</div>';
}
}
});
}
return api;
})();
And that's it. Personally I invoke this in php by calling gravity_form function:
<?php echo gravity_form(1, false, false, false, '', true, 12); ?>
But I assume that a basic embedded form in the WYSIWYG would work also.
I didn't write this myself so I am not sure if I could answer questions, but if you have them feel free to ask