Make a hook to be executed only on "onClick" button - hook

I have a Prestashop module, and I want to execute a hook (inserting a product) just when clicking on a button.
Here is what I'm doing right now :
In the module.php file I'm using this function :
public function hookActionProductAdd()
{
//code to create a product
}
In the module.tpl file I'm creating a button, its onClick execute the hook :
<button onclick="createProduct()">Create product</button>
And at the end of the tpl file I add the script code :
<script>
function createProduct() {
{hook h='ActionProductAdd'}
}
</script>
The problem is the hook is executed every page access/reload, and I want it to execute only when clicking on the button.

A hook is only executed at runtime, when Prestashop generates the template files. Here you want to create an ajax function in your module.
Your module files will look like this :
- mymodule.php
- ajax/
- my_module_ajax.php
- js/
- my_module.js
- views/
- templates/
- front/
- my_module_template.tpl
In file mymodule.php you've got:
<?php
if (!defined('_PS_VERSION_'))
exit;
class MyModule extends Module
{
public function __construct()
{
[...]
}
public function install()
{
if (!parent::install() || !$this->registerHook('header'))
return false;
return true;
}
public function hookHeader($params)
{
$this->context->controller->addJS(($this->_path).'js/my_module.js');
}
public function _ajax_create_product($params)
{
[...]
return $result;
}
}
In file my_module_ajax.php you've got:
<?php
require_once(dirname(__FILE__).'/../../../config/config.inc.php');
require_once(dirname(__FILE__).'/../../../init.php');
require_once(dirname(__FILE__).'/../mymodule.php');
$context = Context::getContext();
// Instance of module class
$module = new MyModule();
switch (Tools::getValue('action'))
{
case 'createProduct':
echo $module->_ajax_create_product(Tools::getValue('test'));
break;
default:
die('error');
}
In file my_module.js you've got:
$(document).ready(function(){
$(document).on('click', '#myButton', function(){
createProduct('a_value');
});
});
function createProduct(value) {
$.ajax({
type: 'GET',
url: baseDir + 'modules/mymodule/ajax/my_module_ajax.php?rand=' + new Date().getTime(),
headers: { "cache-control": "no-cache" },
async: true,
cache: false,
data: 'action=createProduct&value=' + value+ '&other=' + 'test',
success: function(data)
{
console.log("product created");
}
});
}
In file my_module_template.tpl you've got:
<button id="myButton" name="myButton">Create product!</button>
This code is not tested and should be adapted to your needs but the overall concept is here.

Related

Can't get html element using js file in SPFX

I am trying to build dynamic content from a SharePoint list using SPFX. I'd like to use jQuery to build an accordion view of the data. The issue is that I can't even seem to get the element once the page is rendered.
In my code I am requiring a file called ota.js with the following code:
console.log('Start');
function otaExpand(){
console.log('otaExpand Function Called');
let spListContainer = document.getElementById('spListContainer');
console.log(spListContainer);
}
window.addEventListener("load", otaExpand());
In my ts file this is my render method:
public render(): void {
this.domElement.innerHTML = `
<div>
<div id="spListContainer">TEST</div>
</div>
`;
//this._renderListAsync();
//($('.accordion', this.domElement) as any).accordion();
}
When I review the console, I get my messages, but the element itself comes back as null.
console.log
I am using SharePoint 2019 on premise with the following configuration.
+-- #microsoft/generator-sharepoint#1.10.0
+-- gulp-cli#2.3.0
`-- yo#2.0.6
node --version
v8.17.0
I should also mention I am using TypeScript with no JavaScript framework.
Does anyone know why I can't access this element from my js file?
Thanks!
My overall goal is to call list data and apply an accordion style to it (https://jqueryui.com/accordion), but I can't even get passed capturing the element to change it.
I've tried calling my code from a js file as well as trying to put the code directly in the html. Neither worked.
OK, I finally figured out what I was doing wrong. I was calling my jQuery in the render() method rather than in _renderList where this.domElement actually makes sense.
Here's my code in case anyone wants to avoid the pain I put myself through. This allows you to specify a list in the site and you just need to add the fields you want to display.
import { Version } from '#microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneChoiceGroupOption,
IPropertyPaneConfiguration,
PropertyPaneChoiceGroup,
PropertyPaneCustomField,
PropertyPaneTextField
} from '#microsoft/sp-webpart-base';
import { escape } from '#microsoft/sp-lodash-subset';
import styles from './GetSpListItemsWebPart.module.scss';
import * as strings from 'GetSpListItemsWebPartStrings';
import {
SPHttpClient,
SPHttpClientResponse
} from '#microsoft/sp-http';
import * as jQuery from 'jquery';
import 'jqueryui';
import { SPComponentLoader } from '#microsoft/sp-loader';
import PropertyPane from '#microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane';
export interface IGetSpListItemsWebPartProps {
title: string;
description: string;
listField: string;
}
export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
ID: string;
Title: string;
Website: {
Description : string,
Url : string
};
Description : string;
}
export default class GetSpListItemsWebPart extends BaseClientSideWebPart<IGetSpListItemsWebPartProps> {
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('" + this.properties.listField + "')/Items",SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _renderListAsync(): void {
this._getListData()
.then((response) => {
this._renderList(response.value);
})
.catch(() => {});
}
private _renderList(items: ISPList[]): void {
let listData = `
<h1>${this.properties.title}</h1>
<h2>${this.properties.description}</h2>
<div class="accordion">
`;
items.forEach((item: ISPList) => {
let Description : string;
item.Description ? Description = item.Description : Description = "";
listData += `
<h3> ${item.Title}</h3>
<div>
<table>
<tr>
<td>OTA URL</td>
<td>${item.Website.Description}</td>
</tr>
<tr>
<td>Description</td>
<td>${Description}</td>
</tr>
</table>
</div>
`;
});
listData += '</div>';
this.domElement.innerHTML = listData;
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: true,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};
jQuery('.accordion', this.domElement).accordion(accordionOptions);
}
public render(): void {
this._renderListAsync();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title',{
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('listField', {
label: strings.ListFieldLabel
})
]
}
]
}
]
};
}
public constructor() {
super();
SPComponentLoader.loadCss('//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css');
}
}
Your code from the "ota.js" file is probably called before your HTML is initialized (i.e. before the "render()" function is executed). To make sure this is the case, you could add log to the "render()" function to see when it's called.
In other words, "window.load" event happens long before "render()" function is called. This is how web parts are loaded - dynamically after full load of the page. Or "window.load" does not happen at all - web parts may be loaded by the user when using the page designer, i.e. without page reload.
To fix the issue, you should get the element after it's created, i.e. after the "render()" function creates the element you are trying to get.

search engine laravel and vue.js without scout

Hi am trying to make search engine with laravel and vue.js but i have no result:
this is my SearchController.php
namespace Amp\Http\Controllers;
use Amp\User;
use Illuminate\Http\Request;
class SearchController extends Controller
{
/**
* #param Request $request
* #return array
*/
public function search(Request $request)
{
$error = ['error' => 'No results found, please try with different keywords.'];
if ($request->has('q')) {
$users = User::search($request->get('q'))->get();
return $users->count() ? $users : $error;
}
return $error;
}
}
this my TopNavbar.vue:
<template>
<div>
<input type="text" v-model="keywords">
<ul v-if="results.length > 0">
<li v-for="result in results" :key="result.id" v-text="result.name"></li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
keywords: null,
results: []
};
},
watch: {
keywords(after, before) {
this.fetch();
}
},
methods: {
fetch() {
axios.get('api/search', { params: { keywords: this.keywords } })
.then(response => this.results = response.data)
.catch(error => {});
}
}
}
</script>
If i use only the api url then i have result and work proprely i mean if i make search with url on the browser something like this: api/search?q=XXXX then work pefect but only on browser wen i try to make search on then nothing
thank you for your help
To get the keywords sent from axios inside the controller, you would need to use
$keywords = $request->get('keywords');
In the code shared, you are looking for a request parameter named q. When you are entering the URL through the browser, you are entering the parameter with the name q. So the search works. I hope you are clear about the issue now.
So, assuming that you are handling the search method with eloquent, the controller action becomes:
public function search(Request $request)
{
$error = ['error' => 'No results found, please try with different keywords.'];
$keywords = $request->get('keywords')?? null;
if ($keywords) {
$users = User::search($keywords)->get();
return $users->count() ? $users : $error;
}
return $error;
}
For send Request as ajax you must use X-CSRF-Token or disable (exception) validate this token for this url.
For API url validate token disabled.
Read more:
https://laravel.com/docs/5.6/csrf

can't test ember component that appends a div to the dom

I have a ember-cli-addon that adds a component which appends a div with a specific class to the consuming application. I'm trying to test this integration and having difficulty to setup the test.
I have tried to unit test the component as well but that doesn't work quite as expected. Here's what I've tried:
I've copied the component from my addon directory to tests/dummy/components/jquery-backstretch.js to make it available to the dummy test application:
jquery-backstretch.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'jquery-backstretch',
image: null,
selector: 'body',
fade: 0,
duration: 5000,
centeredX: true,
centeredY: true,
setupJquerybackstretch: function() {
var image = this.get('image');
if (! Ember.isEmpty(image)) {
var options = {
fade: this.get('fade'),
centeredX: this.get('centeredX'),
centeredY: this.get('centeredY')
};
var jqbsImage;
if (Ember.typeOf(image) === 'string') {
jqbsImage = 'assets/' + image;
} else if (Ember.isArray(image)) {
options.duration = this.get('duration');
jqbsImage = image.map(function(img) {return 'assets/' + img;});
} else {
Ember.Logger.error('Ember JQuery-Backstretch: Unsupported "image" format.');
}
Ember.$(this.get('selector')).backstretch(jqbsImage, options);
} else {
Ember.Logger.error('Ember JQuery-Backstretch: image not supplied.');
}
}.on('didInsertElement'),
teardownJquerybackstretch: function() {
Ember.$(this.get('selector')).backstretch('destroy');
}.on('willDestroyElement')
});
this causes the component to append the img to the body of the test page and not to #ember-testing-container, changing the selector to #ember-testingn-container puts the img in the right place but the test can't find it:
tests/acceptance/jquery-backstretch.js
import Ember from 'ember';
import {
module,
test
} from 'qunit';
import startApp from '../../tests/helpers/start-app';
var application;
module('Acceptance: JqueryBackstretch', {
beforeEach: function() {
application = startApp();
},
afterEach: function() {
// Ember.run(application, 'destroy');
}
});
test('backstretch added to body tag', function(assert) {
visit('/');
andThen(function() {
assert.equal(find('.backstretch > img').length, 1, 'Backstretch found');
});
});
application.hbs
<h2 id="title">Welcome to Ember.js</h2>
{{jquery-backstretch image="img/emberjs.png"}}
{{outlet}}
the test is not passing, it can't find the image, I also tried to test the component and append it to the DOM then test to see if it's in the DOM but that didn't yield better results.
How can I test this please?

Toastr is not displaying the way it should

toastr is showing an odd behavior -- it's being displayed in a rather ugly way, and I am not overriding anything. No options are given on how to style, but still I am getting this ugly notification.
This is what it looks like:
I am pulling toastr through requireJS; I don't know if that even matters.
logger.js
define(['durandal/system', 'toastr'], function (system, toastr) {
var logger = {
log: log,
logError: logError
};
return logger;
function log(message, data, source, showToast) {
logIt(message, data, source, showToast, 'info');
}
function logError(message, data, source, showToast) {
logIt(message, data, source, showToast, 'error');
}
function logIt(message, data, source, showToast, toastType) {
source = source ? '[' + source + '] ' : '';
if (data) {
system.log(source, message, data);
} else {
system.log(source, message);
}
if (showToast) {
if (toastType === 'error') {
toastr.error(message);
} else {
toastr.info(message);
}
}
}
});
main.js
requirejs.config({
baseUrl: '../Scripts',
paths: {
'services': '../App/services',
'viewmodels': '../App/viewmodels',
'views': '../App/views',
'config': '../App/config',
'durandal': 'durandal',
'plugins': 'durandal/plugins',
'transitions': 'durandal/transitions',
'text': 'text',
'toastr': 'toastr'
}
});
define('jquery', function () { return jQuery; });
define('knockout', ko);
define('main', ['durandal/system', 'durandal/app', 'durandal/viewLocator', 'plugins/router', 'services/logger'], function (system, app, viewLocator, router, logger) {
//>>excludeStart("build", true);
system.debug(true);
//>>excludeEnd("build");
app.title = 'Prepare to die';
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
// Router will use conventions for modules
// assuming viewmodels/views folder structure
router.makeRelative({ moduleId: 'viewmodels' });
// Replace 'viewmodels' in the moduleId with 'views' to locate the view.
// look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
// Show the app by setting the root view model for our application with a transition.
app.setRoot('viewmodels/shell', 'entrance');
// Override bad route behavior to write to
// console log and show error toast
router.handleInvalidRoute = function (route, params) {
logger.logError('No route found', route, 'main', true);
};
});
});
shell.js
define(['durandal/system', 'services/logger', 'plugins/router', 'config'],
function (system, logger, router, config) {
var shell = {
activate: activate,
router: router
};
return shell;
function activate() {
logger.log('Application is Loaded!', null, system.getModuleId(shell), true);
router.map(config.routes).buildNavigationModel();
return router.activate();
}
});
shell.html
<div>
<header>
<!-- ko compose: {view: 'navigation'} -->
<!-- /ko -->
</header>
<section id="content" class="main container-fluid">
<!-- ko compose: {model: router.activeItem, afterCompose: router.afterCompose} -->
<!-- /ko -->
</section>
</div>
Just as a sidebar, we use toastr under Durandal and I know from John Papa's writings that he feels that third-party frameworks should be loaded globally, while our own modules should be loaded modularly. Just food for thought. I can tell that switching to a global model for third-party frameworks eliminated a lot of esoteric issues.
A quick work-around fix is to do the following:
toastr.options.toastClass = 'toastr';

How to set a property of an object and using it inside RequireJS define module

I have an Helper.js file, basically it's an object with a list of global properties used around my Backbone apps.
I need to set some of these properties from the body before they are used inside my Backbone app, I've tried this but it doesn't work:
<script type="text/javascript">
require(["app/helper"], function (Helper) {
console.log('default Helper.webroot = ' + Helper.webroot);
Helper.webroot = "<?php echo $this->webroot;?>";
console.log('require Helper.webroot = ' + Helper.webroot);
});
</script>
console:
default Helper.webroot = /
require Helper.webroot = /site.com/subfolder/
Then I load a define module inside this require:
<script type="text/javascript">
require(["app/models/status_message", "app/views/status_message"], function (StatusMessageModel, StatusMessageView) {
var StatusMessage = new StatusMessageView({
model:new StatusMessageModel({
icon : "<?php echo $icon;?>",
type : "<?php echo $message_type; ?>",
title : "<?php echo __('Informazione'); ?>",
message :"<?php echo __('Nessun messaggio di notifica'); ?>"
})
});
<?php if(!empty($message)) { ?>
StatusMessage.model.set({message:"<?php echo $message; ?>"});
<?php } ?>
});
in app/views/status_message i've tried to to log Helper.webroot:
define([
"jquery",
"handlebars",
"lodash",
"helper",
"backbone",
"app/models/status_message",
"text!app/templates/status_message.php"
],
function ($, Handlebars, _, Helper, Backbone, StatusMessageModel, tmplStatusMessage) {
console.log('define Helper.webroot = ' + Helper.webroot);
return Backbone.View.extend({...});
}
);
console:
define Helper.webroot = /
So I haven't set the webroot before the Backbone app, how can I do that?
You can call require inside the function body of another require:
require(["app/helper"], function (Helper) {
Helper.webroot = "<?php echo $this->webroot;?>";
require(["app/models/status_message", "app/views/status_message"],
function (StatusMessageModel, StatusMessageView) {
// [Helper.webroot is correctly set.]
})
})

Resources