Converting old wordpress shortcode plugin to block - javascript

I have an old wordpress plugin built to show latest posts of choice. The plugin uses a shortcode with options. Now I am converting the plugin little bit so that it can be used as a gutenberg block. I kept the php code intact and trying to create a block which will have some settings (right side setting box for block). I don't want to show the posts in block editor like other blocks instead I will just show the data in frontend. So in admin, nothing will be visible rather than a placeholder text and the settings. In plugin's init.php I have this code (Please ignore coding mistakes here, I just put some part to get the idea):
final class Dcposts {
function dcposts_block_assets() {
register_block_type(
'dc/block-dcposts', array(
'style' => 'dcposts-style-css',
'editor_script' => 'dcposts-block-js',
'editor_style' => 'dcposts-block-editor-css',
'render_callback' => array($this, 'mytest')
)
);
}
public function mytest($attributes) {
return '[some_shortcode]'; // I will generate a dynamic shortcode with $attributes
}
}
This works fine. If I add the block, it shows the posts front-end. But I get an error message in admin: "Updating failed. The response is not a valid JSON response."while saving the page with the block; and also the shortcode executes in admin. How can I prevent this? Is this the right approach I am on? Please give me some idea.

The error message "Updating failed. The response is not a valid JSON response." occurs when your render_callback function returns different content than the Gutenberg Edit() function. For dynamic blocks, the save() function should return null.
The ServerSideRender block is ideal for migrating/integrating existing PHP functions into a Gutenberg block.
You are on the right path with registering your block dc/block-dcposts in PHP. Next, add a ServerSideRender component in the Edit function of your block declaration in JavaScript, eg:
index.js
import { registerBlockType } from '#wordpress/blocks';
import { __ } from '#wordpress/i18n';
import ServerSideRender from '#wordpress/server-side-render';
/**
* Registering our block in JavaScript
*/
registerBlockType('dc/block-dcposts', {
title: __('DCPosts', 'dc'),
description: __(
'What DCPosts does..',
'dc'
),
category: 'layout',
icon: 'star-filled',
edit: () => {
return (
<ServerSideRender
block="dc/block-dcposts"
/>
);
},
save: () => { return null } // Dynamic blocks should return null to avoid validation error.
});
plugin.php (simplified example)
function dc_block_dcposts($attributes){
return '<div>Dynamically rendered content</div>';
}
function dc_block_dcposts_init(){
register_block_type( 'dc/block-dcposts', array(
'render_callback' => 'dc_block_dcposts',
) );
};
add_action( 'init', 'dc_block_dcposts_init' );
Once you have the basic setup working, the next step could be migrating any attributes your PHP function requires. The attributes need to be defined in the block setup and then passed in via the ServerSideRender component to be recieved by your PHP function.

Related

Rails 5/6: How to include JS functions with webpacker?

I am trying to update a Rails 3 app to Rails 6 and I have problems with the now default webpacker since my Javascript functions are not accessible.
I get: ReferenceError: Can't find variable: functionName for all js function triggers.
What I did is:
create an app_directory in /app/javascript
copied my development javascript file into the app_directory and renamed it to index.js
added console.log('Hello World from Webpacker'); to index.js
added import "app_directory"; to /app/javascript/packs/application.js
added to /config/initializers/content_security_policy.rb:
Rails.application.config.content_security_policy do |policy|
policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
end
I get 'Hello World from Webpacker' logged to console, but when trying to access a simple JS function through <div id="x" onclick="functionX()"></div> in the browser I get the reference error.
I understand that the asset pipeline has been substituted by webpacker, which should be great for including modules, but how should I include simple JS functions? What am I missing?
Thanks in advance?
For instructions on moving from the old asset pipeline to the new webpacker way of doing things, you can see here:
https://www.calleerlandsson.com/replacing-sprockets-with-webpacker-for-javascript-in-rails-5-2/
This is a howto for moving from the asset pipeline to webpacker in Rails 5.2, and it gives you an idea of how things are different in Rails 6 now that webpacker is the default for javascript. In particular:
Now it’s time to move all of your application JavaScript code from
app/assets/javascripts/ to app/javascript/.
To include them in the JavaScript pack, make sure to require them in
app/javascript/pack/application.js:
require('your_js_file')
So, create a file in app/javascript/hello.js like this:
console.log("Hello from hello.js");
Then, in app/javascript/packs/application.js, add this line:
require("hello")
(note that the extension isn't needed)
Now, you can load up a page with the browser console open and see the "Hello!" message in the console. Just add whatever you need in the app/javascript directory, or better yet create subdirectories to keep your code organized.
More information:
This question is cursed. The formerly accepted answer is not just wrong but grotesquely wrong, and the most upvoted answer is still missing the mark by a country mile.
anode84 above is still trying to do things the old way, and webpacker will get in your way if you try that. You have to completely change the way you do javascript and think about javascript when you move to webpacker. There is no "scoping issue". When you put code in a web pack it's self-contained and you use import/export to share code between files. Nothing is global by default.
I get why this is frustrating. You're probably like me, and accustomed to declaring a function in a javascript file and then calling it in your HTML file. Or just throwing some javascript at the end of your HTML file. I have been doing web programming since 1994 (not a typo), so I've seen everything evolve multiple times. Javascript has evolved. You have to learn the new way of doing things.
If you want to add an action to a form or whatever, you can create a file in app/javascript that does what you want. To get data to it, you can use data attributes, hidden fields, etc. If the field doesn't exist, then the code doesn't run.
Here's an example that you might find useful. I use this for showing a popup if a form has a Google reCAPTCHA and the user hasn't checked the box at the time of form submission:
// For any form, on submit find out if there's a recaptcha
// field on the form, and if so, make sure the recaptcha
// was completed before submission.
document.addEventListener("turbolinks:load", function() {
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function(event) {
const response_field = document.getElementById('g-recaptcha-response');
// This ensures that the response field is part of the form
if (response_field && form.compareDocumentPosition(response_field) & 16) {
if (response_field.value == '') {
alert("Please verify that you are not a robot.");
event.preventDefault();
event.stopPropagation();
return false;
}
}
});
});
});
Note that this is self-contained. It does not rely on any other modules and nothing else relies on it. You simply require it in your pack(s) and it will watch all form submissions.
Here's one more example of loading a google map with a geojson overlay when the page is loaded:
document.addEventListener("turbolinks:load", function() {
document.querySelectorAll('.shuttle-route-version-map').forEach(function(map_div) {
let shuttle_route_version_id = map_div.dataset.shuttleRouteVersionId;
let geojson_field = document.querySelector(`input[type=hidden][name="geojson[${shuttle_route_version_id}]"]`);
var map = null;
let center = {lat: 36.1638726, lng: -86.7742864};
map = new google.maps.Map(map_div, {
zoom: 15.18,
center: center
});
map.data.addGeoJson(JSON.parse(geojson_field.value));
var bounds = new google.maps.LatLngBounds();
map.data.forEach(function(data_feature) {
let geom = data_feature.getGeometry();
geom.forEachLatLng(function(latlng) {
bounds.extend(latlng);
});
});
map.setCenter(bounds.getCenter());
map.fitBounds(bounds);
});
});
When the page loads, I look for divs with the class "shuttle-route-version-map". For each one that I find, the data attribute "shuttleRouteVersionId" (data-shuttle-route-version-id) contains the ID of the route. I have stored the geojson in a hidden field that can be easily queried given that ID, and I then initialize the map, add the geojson, and then set the map center and bounds based on that data. Again, it's self-contained except for the Google Maps functionality.
You can also learn how to use import/export to share code, and that's really powerful.
So, one more that shows how to use import/export. Here's a simple piece of code that sets up a "watcher" to watch your location:
var driver_position_watch_id = null;
export const watch_position = function(logging_callback) {
var last_timestamp = null;
function success(pos) {
if (pos.timestamp != last_timestamp) {
logging_callback(pos);
}
last_timestamp = pos.timestamp;
}
function error(err) {
console.log('Error: ' + err.code + ': ' + err.message);
if (err.code == 3) {
// timeout, let's try again in a second
setTimeout(start_watching, 1000);
}
}
let options = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 14500
};
function start_watching() {
if (driver_position_watch_id) stop_watching_position();
driver_position_watch_id = navigator.geolocation.watchPosition(success, error, options);
console.log("Start watching location updates: " + driver_position_watch_id);
}
start_watching();
}
export const stop_watching_position = function() {
if (driver_position_watch_id) {
console.log("Stopped watching location updates: " + driver_position_watch_id);
navigator.geolocation.clearWatch(driver_position_watch_id);
driver_position_watch_id = null;
}
}
That exports two functions: "watch_position" and "stop_watching_position". To use it, you import those functions in another file.
import { watch_position, stop_watching_position } from 'watch_location';
document.addEventListener("turbolinks:load", function() {
let lat_input = document.getElementById('driver_location_check_latitude');
let long_input = document.getElementById('driver_location_check_longitude');
if (lat_input && long_input) {
watch_position(function(pos) {
lat_input.value = pos.coords.latitude;
long_input.value = pos.coords.longitude;
});
}
});
When the page loads, we look for fields called "driver_location_check_latitude" and "driver_location_check_longitude". If they exist, we set up a watcher with a callback, and the callback fills in those fields with the latitude and longitude when they change. This is how to share code between modules.
So, again, this is a very different way of doing things. Your code is cleaner and more predictable when modularized and organized properly.
This is the future, so fighting it (and setting "window.function_name" is fighting it) will get you nowhere.
Looking at how webpacker "packs" js files and functions:
/***/ "./app/javascript/dashboard/project.js":
/*! no static exports found */
/***/ (function(module, exports) {
function myFunction() {...}
So webpacker stores these functions within another function, making them inaccessible. Not sure why that is, or how to work around it properly.
There IS a workaround, though. You can:
1) change the function signatures from:
function myFunction() { ... }
to:
window.myFunction = function() { ... }
2) keep the function signatures as is, but you would still need to add a reference to them as shown here:
window.myFunction = myFunction
This will make your functions globally accessible from the "window" object.
Replace the code in your custom java Script file
from
function function_name() {// body //}
to
window.function_name = function() {// body //}
From the official rails app guide:
Where to Stick Your JavaScript
Whether you use the Rails asset
pipeline or add a tag directly to a view, you have to make a
choice about where to put any local JavaScript file.
We have a choice of three locations for a local JavaScript file:
The app/assets/javascripts folder,the lib/assets/javascripts folder and the vendor/assets/javascripts folder
Here are guidelines for selecting
a location for your scripts:
Use app/assets/javascripts for JavaScript you create for your
application.
Use lib/assets/javascripts for scripts that are shared by many
applications (but use a gem if you can).
Use vendor/assets/javascripts for copies of jQuery plugins, etc., from
other developers. In the simplest case, when all your JavaScript files
are in the app/assets/javascripts folder, there’s nothing more you
need to do.
Add JavaScript files anywhere else and you will need to understand how
to modify a manifest file.
More reading:
http://railsapps.github.io/rails-javascript-include-external.html

Wordpress - enqueue script only if specific class is on page

What would be the most appropriate solution to enqueue a script in WordPress only when a class of .google-map is detected on that page?
In my main.js I can detect the item on the page and do something, but I am not so sure you can use the enqueue function in a JS file.
$(document).ready(function(){
if (document.getElementsByClassName('.google-map')) {
alert(true);
}
});
The above is just attempt #1. Please feel free to provide any other solutions, using functions or anything else. I am simply not too sure what is possible that's why I don't have more examples.
Ordinarily add the file google.js (or whatever name you choose) to WP footer by adding the code below into your functions.php file. This will add the javascript file into WP footer the right way. https://developer.wordpress.org/reference/functions/wp_enqueue_script/
function my_scriptings_scripts() {
wp_enqueue_script( 'my_scriptings', get_template_directory_uri() . '/js/google.js', array('jquery'), '20171212', true );
}
add_action( 'wp_enqueue_scripts', 'my_scriptings_scripts' );
Inside your js/google.js
Using Vanilla javascript, check for the element with class name. If it exists then call the function for the action.
var element = document.getElementByClassName('google-map');
if (typeof(element) != 'undefined' && element != null)
{
//call function for google actions
google_acts_like_this();
}
function google_acts_like_this(){
console.log('Google will take over the world');
alert('Google will take over the world');
}
OR Try wienter code hereth Jquery -
if ($(document).find(.google-map).length > 0)
{
//If the element exist, then do something.
google_acts_like_this();
}
function google_acts_like_this(){
console.log('Google will take over the world');
alert('Google will take over the world');
}

Error Call Function On Yii2 Dropdown Active Form

Halo, I using Yii2 and I get error while call a function in dropdown list.
This is my dropdown :
<?= $form->field($model, "jenis_manifest")->dropDownList([ 'Berangkat' => 'Berangkat', 'Pulang' => 'Pulang'], ['onchange' => 'fungsiUtama();', 'class' => 'form-control', 'prompt' => '-- Manifest --']) ?>
And this is my script :
<?php
$script = <<<JS
function fungsiUtama()
{
alert("Hello");
}
JS;
$this->registerJs($script);
?>
I also try with JsExpression base on this question, but still not working.
This is the error what I get :
Use
$this->registerJs($script, \yii\web\View::POS_END);
The last part means - add this script straightforward at the end of page.
Without the second function argument the default one is called -
\yii\web\View::POS_READY - which means - add this script wrapped in jQuery(document).ready() method that forces scripts to wait untill the page's DOM is fully loaded. Before that your script is unavailable and that is probably why you've got this error.

Where to Include Sugarcrm

I have been working on some custom code for a sugar module and am fairly unclear where to put my javascript code to be called in module.
Currently I have put my custom JS in include/javascript/popup_parent_helper.js
This works fine in developer mode but does not work when that is turned off and unfortunately dveloper mode runs SUPER slow
I have done a lot of research and I am getting some conflicting results.
Some tell me that I should include it in:
/modules/[ModuleName]/
Others say that it should to in:
/custom/modules/[ModuleName/
and some further in adding js as a directory
Please help me clarify proper structure for this and where I need to make my proper include statement
Clarifications:
We are using SugarCrm 6.5x
In this case the JS is only being used for one module.
It is being used in the Quick Create View and the Edit View
If the javascript should be accessible by any module, you can create a new JSGrouping and pull in your custom js file using the following technique:
http://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_7.6/Extension_Framework/JSGroupings/#Creating_New_JSGroupings
It sounds like you want it to be isolated to your custom module, so you should probably extend the view desired. If you are extending the record view, create a new file called record.js at custom/modules/-your_module-/clients/base/views/record/
({
extendsFrom: 'RecordView',
initialize: function(options) {
this._super('initialize', [options]);
this.doSomething();
},
doSomething: function(){
console.log("Help you I will");
},
...
})
https://developer.sugarcrm.com/2014/02/10/extending-sugar-7-record-view/
I had also faced similar issue (JS should be work for edit and quickcreate form) but after making some RnD I achieved it as per following way:
\custom\modules\<modulename>\views\view.edit.php
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('include/MVC/View/views/view.edit.php');
class {moduleName}ViewEdit extends ViewEdit {
public function __construct() {
parent::ViewEdit();
$this->useForSubpanel = true; // this variable specifies that these changes should work for subpanel
// / $this->useModuleQuickCreateTemplate = true; // quick create template too
}
function display(){ ?>
<?php
$jsscript = <<<EOQ
<script>
Your JS code
</script>
EOQ;
parent::display();
echo $jsscript; //echo the script
}
}
?>

Adding page-specific Javascript to each view in CakePHP

In an attempt to keep my scripts maintainable, I'm going to move each into their own file, organised by controller and action:
// scripts which only apply to /views/posts/add.ctp
/app/webroot/js/page/posts/add.js
// scripts which only apply to /view/users/index.ctp
/app/webroot/js/page/users/index.js
That's all cool, however I'd like for these to be automatically added by the Controller, since it obviously knows the name of both the controller and action.
I figure the best place for this is in AppController::beforeRender(). (yes?)
The only problem is that I don't know how to actually add this into the $scripts_for_layout variable. I thought that getting a reference to the javascript helper object would work, but I can't find it from the controller!
class AppController extends Controller {
var $helpers = array("javascript", "html", "form");
function beforeRender() {
// ???
}
}
Very easy to do in your default.ctp layout file:
An example to automatically include .css files per controller and/or controller/action (because I had this lying around, easily adaptable to .js files):
<head>
...
<?php
if (is_file(WWW_ROOT . 'css' . DS . $this->params['controller'] . '.css')) {
echo $html->css($this->params['controller']);
}
if (is_file(WWW_ROOT . 'css' . DS . $this->params['controller'] . DS . $this->params['action'] . '.css')) {
echo $html->css($this->params['controller'] . '/' . $this->params['action']);
}
?>
...
</head>
Like deceze is saying, we do it using the layout, although I find our solution a bit more elegant :)
In default.ctp:
if(isset($cssIncludes)){
foreach($cssIncludes as $css){
echo $html->css($css);
}
}
if(isset($jsIncludes)){
foreach($jsIncludes as $js){
echo $javascript->link($js);
}
}
Then, in our controller actions, we define these arrays:
$this->set('cssIncludes',array('special')); // this will link to /css/special.css
$this->set('jsIncludes',array('jquery')); // this will link to /js/jquery.js
For files that need to be loaded in each view, we simply add the same type of link "statically" to the top of the layout, like:
echo $javascript->link('global');
echo $html->css('global');
This works really well for us. Good luck!
Kinda new to this, but after reading this added the following to my layout:
if ($handle = opendir(WWW_ROOT . 'js' . DS . $this->params['controller'] . DS . $this->params['action']))
{
while (false !== ($entry = readdir($handle)))
{
$entry = str_replace(".js", "", $entry);
echo $this->Html->script($entry);
}
closedir($handle);
}
I just had to do page specific inclusion, but I've found a neater way to do it in the documentation. You can just load it into some script block in your default.ctp. And in corresponding view just use HTML helper to push a script:
You can append the script tag to a specific block using the block
option:
echo $this->Html->script('wysiwyg', array('block' => 'scriptBottom'));
Which appends <script type="text/javascript" href="/js/wysiwyg.js"></script> to a block.
In your layout you can output all the script tags added to
‘scriptBottom’:
echo $this->fetch('scriptBottom');
The best way I can think of is to create your own custom AppView and have all your controllers use that:
class myController extends AppController {
var view = 'AppView';
...
}
Then, somewhere in your AppView, you'd want to do something like:
function __construct(&$controller, $register){
parent::__construct($controller,$register);
$this->addScript('<script type="text/javascript" src="/path/to/js/' . $this->controller . '/' . $this->action . '.js"></script>');
}
But I'd take a step back and think about a few things, first.
How big are your scripts, on average? Is the overhead of an external script call (before the script is cached by the client) better than adding a few hundred bytes to your main output stream (by just sticking the script into the page, inline)?
Perhaps you'd be better of somewhere in the middle -- split your scripts up by controller, but not action. That way, after the first visit to any action, the client has all scripts for all actions. This way, you avoid a big initial download for all the application's script, but you avoid adding N http round-trips (where N is the number of actions a brand new user visits).
Another way to approach the problem is to do it all in javascript. Just figure out a lazy-loading scheme. So your app just loads a very small loader.js, and that script figures out which other javascript sources to pull in.
Note: I've never tested my extend-the-view hack, but I bet it'll work if you really want to do this.
There's a nuts and bolts of CakePHP blog post on Convention over Configuration – what's the big deal? - which uses a helper for specifying Javascript files:
<?php
class JsManagerHelper extends AppHelper {
var $helpers = array('Javascript');
//where's our jQuery path relevant to CakePHP's JS dir?
var $_jqueryPath = 'jquery';
//where do we keep our page/view relevant scripts?
var $_pageScriptPath = 'page_specific';
function myJs() {
return $this->Javascript->link($this->_jqueryPath . '/' .
$this->_pageScriptPath .'/' .
$this->params['controller'] . '_' .
$this->params['action'], false);
}
}
?>
And then you just have $jsManager->myJs(); in your view.
I have authored a Plugin for this exact issue.
If you have two files:
/app/View/Post/add.ctp
/app/View/Post/add.js
it will include the add.js into the script block of the page.
OR
app/View/Post/add.js
app/webroot/js/post/add.js
it will include /js/post/add.js into the script block of the page.
A few cute features are in there and it's simple as beans. You can even use PHP inside your .js files and use viewVars which you set in the action. Most of all, you don't need to hack the view or layout files to make it work, simply fetch the 'script' block in the layout. You can also simply write the .js to another view block.
You can check it out here: https://github.com/dizyart/cakephp-viewautoload
It's in the early stages, so make sure to comment and wishlist!
LI worked a little (very little) bit over W1ckd snippet and made it easier for sharing the same js for different actions:
if ( is_dir(WWW_ROOT . 'js' . DS . $this->params['controller'] ) && ( $handle = opendir( WWW_ROOT . 'js' . DS . $this->params['controller'] ) ) )
{
while ( false !== ( $entry = readdir( $handle ) ) )
{
if ( in_array( $this->params['action'], explode( '.', $entry ) ) ) {
$entry = str_replace( ".js", "", $entry );
echo $this->Html->script( $this->params['controller'].DS.$entry );
}
}
closedir( $handle );
}
This way you can have something like:
webroot/js/controller/view.edit.add.js
And this js will be included in those three actions (view, edit, add).
With Cake v3 you can do it like this. Add the scripts you want in the specific controller.
//YourController.php
public function beforeRender (Event $event)
{
array_push($this->scripts, 'Admin/gallery');
parent::beforeRender($event);
}
Set the default in intialize and render once
//AppController.php
public function initialize()
{
parent::initialize();
$this->scripts = [];
}
public function beforeRender (Event $event)
{
/* Define scripts in beforeRender of Child */
$this->set('scripts', $this->scripts);
/*-----------------------------------------*/
}
Now you can run this function once.
<?= $this->Html->script($scripts) ?>

Categories