refactored user blocks using ajax loaded drupal's html as template for vue

This commit is contained in:
Bachir Soussi Chiadmi 2019-05-31 23:05:37 +02:00
parent a48b7262eb
commit d21bd5ef4e
22 changed files with 492 additions and 225 deletions

View File

@ -9,7 +9,7 @@ dependencies:
id: languageswitcher id: languageswitcher
theme: materiotheme theme: materiotheme
region: header_right region: header_right
weight: 0 weight: -1
provider: null provider: null
plugin: 'language_block:language_interface' plugin: 'language_block:language_interface'
settings: settings:

View File

@ -1,20 +1,28 @@
uuid: f6ac7bc7-be23-4375-a237-1d9a36edb17e uuid: 7d7e2454-1d8e-4e95-8110-985088634608
langcode: en langcode: en
status: true status: true
dependencies: dependencies:
module: module:
- user_block - materio_user
- user
theme: theme:
- materiotheme - materiotheme
id: userblock id: userblock
theme: materiotheme theme: materiotheme
region: header_right region: header_right
weight: -1 weight: -2
provider: null provider: null
plugin: user_block plugin: user_block
settings: settings:
id: user_block id: user_block
label: 'User block' label: 'User block'
provider: user_block provider: materio_user
label_display: visible label_display: visible
visibility: { } visibility:
user_role:
id: user_role
roles:
authenticated: authenticated
negate: false
context_mapping:
user: '@user.current_user_context:current_user'

View File

@ -9,7 +9,7 @@ dependencies:
id: userlogin id: userlogin
theme: materiotheme theme: materiotheme
region: header_right region: header_right
weight: -2 weight: -3
provider: null provider: null
plugin: user_login_block plugin: user_login_block
settings: settings:

View File

@ -96,6 +96,7 @@ module:
materio_migrate: 0 materio_migrate: 0
materio_samples: 0 materio_samples: 0
materio_sapi: 0 materio_sapi: 0
materio_user: 0
matomo: 0 matomo: 0
maxlength: 0 maxlength: 0
menu_block: 0 menu_block: 0
@ -146,7 +147,6 @@ module:
update: 0 update: 0
url_to_video_filter: 0 url_to_video_filter: 0
user: 0 user: 0
user_block: 0
video_embed_field: 0 video_embed_field: 0
video_embed_wysiwyg: 0 video_embed_wysiwyg: 0
views_bulk_edit: 0 views_bulk_edit: 0

View File

@ -13,7 +13,7 @@ materio_sapi.search_form:
_format: json _format: json
requirements: requirements:
_permission: 'access materio search' _permission: 'access materio search'
#
# materio_sapi.materio_sapi_search_form: # materio_sapi.materio_sapi_search_form:
# path: '/materio_sapi/form/materio_sapi_search' # path: '/materio_sapi/form/materio_sapi_search'
# defaults: # defaults:
@ -21,4 +21,3 @@ materio_sapi.search_form:
# _title: 'MaterioSapiSearchForm' # _title: 'MaterioSapiSearchForm'
# requirements: # requirements:
# _access: 'TRUE' # _access: 'TRUE'
#

View File

@ -0,0 +1,5 @@
name: 'materio_user'
type: module
description: ''
core: 8.x
package: 'Materio'

View File

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains materio_user.module.
*/
use \Drupal\Core\Form\FormStateInterface;
use \Drupal\Core\Block\BlockPluginInterface;
/**
* implements hook_form_FORM_ID_alter()
*
*/
function materio_user_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Drupal::logger('materio_user')->notice(print_r($form, true));
$form['name']['#attributes'] += array(
"v-model" => "mail",
"@keyup.enter" => "login"
);
$form['pass']['#attributes'] = array(
"v-model" => "password",
"@keyup.enter" => "login"
);
$form['actions']['submit']['#attributes'] = array(
"@click.prevent" => "login"
);
}
/**
* implements hook_block_view_BASE_BLOCK_ID_alter()
*
* https://www.drupal.org/project/drupal/issues/2626224
*/
function materio_user_block_view_user_login_block_alter(array &$build, BlockPluginInterface $block) {
$build['#pre_render'][] = '_materio_user_user_login_block_pre_render';
}
function _materio_user_user_login_block_pre_render(array $build){
$user_links = &$build['content']['user_links'];
$items = &$user_links['#items'];
// ksm($items);
$items['create_account']['#url']->mergeOptions(array(
"attributes" => array(
"@click.prevent" => "create_account"
)
));
$items['request_password']['#url']->mergeOptions(array(
'attributes' => array(
"@click.prevent" => "request_password"
)
));
return $build;
}

View File

@ -0,0 +1,15 @@
materio_user.login_form:
path: '/materio_user/login_form'
defaults:
_controller: '\Drupal\materio_user\Controller\AjaxLoginForm::getForm'
_format: json
requirements:
_access: 'TRUE'
materio_user.login_block:
path: '/materio_user/login_block'
defaults:
_controller: '\Drupal\materio_user\Controller\AjaxLoginBlock::getBlock'
_format: json
requirements:
_access: 'TRUE'

View File

@ -0,0 +1,54 @@
<?php
namespace Drupal\materio_user\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Drupal\block\Entity\Block;
use Symfony\Component\HttpFoundation\JsonResponse;
// use Drupal\Core\Cache\CacheableJsonResponse;
// use Drupal\Core\Cache\CacheableMetadata;
// use Drupal\core\render\RenderContext;
/**
* Defines a route controller.
*/
class AjaxLoginBlock extends ControllerBase {
private function getBlockDefinition(){
// $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
// \Drupal::logger('materio_user')->notice($language);
$this->bid = "userlogin";
$this->block = Block::load($this->bid);
$this->block_builded = \Drupal::entityManager()->getViewBuilder('block')->view($this->block);
}
/**
* Handler for getBlock request.
*/
public function getBlock(Request $request) {
$this->getBlockDefinition();
$rendered = \Drupal::service('renderer')->renderRoot($this->block_builded);
$data = [
'rendered' => $rendered,
// '#cache' => [
// 'max-age' => \Drupal\Core\Cache\Cache::PERMANENT,
// 'tags' => [
// 'materio_sapi-search_form-cache',
// ]
// ]
];
$response = new JsonResponse();
$response->setData($data);
// $response = new CacheableJsonResponse($data);
// $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($data));
return $response;
}
}

View File

@ -0,0 +1,82 @@
<?php
// https://www.qed42.com/blog/autocomplete-drupal-8
// https://www.drupal.org/docs/8/modules/search-api/developer-documentation/executing-a-search-in-code
namespace Drupal\materio_user\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Form\FormState;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
// use Drupal\Core\Cache\CacheableJsonResponse;
// use Drupal\Core\Cache\CacheableMetadata;
use Drupal\core\render\RenderContext;
/**
* Defines a route controller for entity autocomplete form elements.
*/
class AjaxLoginForm extends ControllerBase {
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilder
*/
protected $formBuilder;
/**
* Construct.
*/
public function __construct(FormBuilder $formBuilder) {
$this->formBuilder = $formBuilder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('form_builder')
);
}
private function getFormDefinition(){
// $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
// \Drupal::logger('materio_user')->notice($language);
$this->form_builded = $this->formBuilder->getForm('Drupal\user\Form\UserLoginForm');
}
/**
* Handler for getform request.
*/
public function getForm(Request $request) {
$this->getFormDefinition();
$rendered = render($this->form_builded);
// $form_builded = $this->form_builded;
// $rendered = \Drupal::service('renderer')->executeInRenderContext(new RenderContext(), function () use ($form_builded) {
// return render($form_builded);
// });
$data = [
'rendered' => $rendered,
// '#cache' => [
// 'max-age' => \Drupal\Core\Cache\Cache::PERMANENT,
// 'tags' => [
// 'materio_sapi-search_form-cache',
// ]
// ]
];
$response = new JsonResponse();
$response->setData($data);
// $response = new CacheableJsonResponse($data);
// $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($data));
return $response;
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Drupal\user_block\Plugin\Block; namespace Drupal\materio_user\Plugin\Block;
use Drupal\Core\Session\AccountProxy; use Drupal\Core\Session\AccountProxy;
use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Session\AccountProxyInterface;

View File

@ -1,5 +0,0 @@
name: 'user_block'
type: module
description: 'Provide a block with user name/email and logout link'
core: 8.x
package: 'Custom'

View File

@ -1,24 +0,0 @@
<?php
/**
* @file
* Contains user_block.module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
// function user_block_help($route_name, RouteMatchInterface $route_match) {
// switch ($route_name) {
// // Main module help for the user_block module.
// case 'help.page.user_block':
// $output = '';
// $output .= '<h3>' . t('About') . '</h3>';
// $output .= '<p>' . t('Provide a block with user name/email and logout link') . '</p>';
// return $output;
//
// default:
// }
// }

File diff suppressed because one or more lines are too long

View File

@ -30,45 +30,27 @@ import 'theme/assets/styles/main.scss'
} }
function initVues(){ function initVues(){
initUserVBlock() initVUserBlock()
initVMainContent() initVMainContent()
initVSearchBlock() initVSearchBlock()
} }
function initUserVBlock(){ function initVUserBlock(){
let mount_point = drupalSettings.user.uid !== 0 ? '#block-userblock' : '#block-userlogin'; let mount_point = drupalSettings.user.uid !== 0 ? 'block-userblock' : 'block-userlogin';
let props = { let props = {
title: "", title: "",
form:"" loginblock:""
}; };
switch (mount_point) { switch (mount_point) {
case '#block-userlogin': case 'block-userlogin':
// let $form = document.getElementById('user-login-form'); let $block = document.getElementById(mount_point);
// console.log('login form html', $form); console.log('initVUserBlock login form html', $block);
// props.form = $form.outerHTML props.loginblock = $block.outerHTML.trim()
let $block = document.querySelector(mount_point);
props = {
title: $block.querySelector('h2').textContent,
form: {
ph_email: $block.querySelector('input#edit-name').getAttribute('placeholder'),
ph_pass: $block.querySelector('input#edit-pass').getAttribute('placeholder'),
btn_value: $block.querySelector('input#edit-submit').getAttribute('value'),
register: {
title: $block.querySelector('a.create-account-link').textContent,
href: $block.querySelector('a.create-account-link').getAttribute('href')
},
reset: {
title: $block.querySelector('a.request-password-link').textContent,
href: $block.querySelector('a.request-password-link').getAttribute('href')
}
}
}
break; break;
case '#block-userblock': case 'block-userblock':
default: default:
break; break;
} }
// console.log(props);
_v_user_block = new Vue({ _v_user_block = new Vue({
store, store,
@ -78,14 +60,15 @@ import 'theme/assets/styles/main.scss'
// }) // })
// }, // },
created () { created () {
// if already loggedin, call store.user to get the user infos
if(drupalSettings.user.uid !== 0){ if(drupalSettings.user.uid !== 0){
this.$store.commit('User/setUid', drupalSettings.user.uid) this.$store.commit('User/setUid', drupalSettings.user.uid)
this.$store.dispatch('User/getUser') this.$store.dispatch('User/getUser')
} }
}, },
render: h => h(VUserBlock, {props:props}) render: h => h(VUserBlock, {props:props})
}).$mount(mount_point) }).$mount('#'+mount_point)
// console.log('initUserVBlock', _v_user_block); // console.log('initVUserBlock', _v_user_block);
} }
function initVMainContent(){ function initVMainContent(){

View File

@ -52,7 +52,7 @@ header[role="banner"]{
&>section{ &>section{
background-color: #fff; background-color: #fff;
overflow: hidden; overflow: hidden;
width:14em; width:11em;
height:1px; height:1px;
padding:0.01em 1em; padding:0.01em 1em;
// margin:0 0 0 -1em; // margin:0 0 0 -1em;
@ -63,6 +63,7 @@ header[role="banner"]{
position: absolute; position: absolute;
right:0; right:0;
top:1.7em; top:1.7em;
box-sizing: content-box;
} }
&:hover{ &:hover{
&>section{ &>section{
@ -72,6 +73,48 @@ header[role="banner"]{
box-shadow: 0 0 10px #ccc; box-shadow: 0 0 10px #ccc;
} }
} }
.form-item{
margin:0;
position: relative;
width:100%;
&.form-item-name{
margin:2px 0 0.5em 0;
input{
width:90%;
padding:0;
box-sizing: content-box;
}
}
&.form-item-pass{
margin:0 0 0.5em 0;
input{
width:90%;
padding:0;
box-sizing: content-box;
}
}
&.form-item-persistent-login{
font-size: 0.756em;
label{
vertical-align: top;
}
}
}
#edit-actions{
margin:0;
}
.item-list{
ul{
margin:0;
li{
list-style: none;
margin:0;
a{
font-size: 0.756em;
}
}
}
}
} }
#block-languageswitcher{ #block-languageswitcher{

View File

@ -4,8 +4,8 @@ import axios from 'axios'
// console.log('drupalSettings', drupalSettings); // console.log('drupalSettings', drupalSettings);
export const MSAPI = axios.create({ export const MA = axios.create({
baseURL: window.location.origin + '/' + drupalSettings.path.pathPrefix + `materio_sapi`, baseURL: window.location.origin + '/' + drupalSettings.path.pathPrefix,
withCredentials: true, withCredentials: true,
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"

View File

@ -0,0 +1,84 @@
<script>
import Vue from 'vue'
import { mapState, mapActions } from 'vuex'
export default {
props: ['title', 'block'],
data () {
return {
template: null,
mail: '',
password: ''
}
},
computed: {
...mapState(['User'])
},
methods: {
...mapActions({
userLogin: 'User/userLogin'
}),
login () {
this.userLogin({
mail: this.mail,
pass: this.password
})
},
request_password () {
console.log('request_password');
},
create_account () {
console.log('create_account');
}
},
beforeMount() {
console.log('LoginBlock beforeMount', this._props.block);
if(this._props.block){
// console.log('LoginBlock beforeMount if this._props.block ok');
this.template = Vue.compile(this._props.block)
this.$options.staticRenderFns = [];
this._staticTrees = [];
this.template.staticRenderFns.map(fn => (this.$options.staticRenderFns.push(fn)));
}
},
mounted(){
// console.log('LoginBlock mounted');
Drupal.attachBehaviors(this.$el);
},
render(h) {
// console.log('LoginBlock render');
if(!this.template){
// console.log('LoginBlock render NAN');
return h('span', 'Loading ...')
}else{
// console.log('LoginBlock render template');
return this.template.render.call(this)
}
}
}
</script>
<style lang="scss" scoped>
// section{
// max-width:300px;
// }
// input{
// display:block;
// max-width:100%;
// margin:0.5em 0 0 0;
// }
// button{
// margin:0.5em 0 0 0;
// }
// ul{
// margin:0; padding:0.5em 0 0 0;
// }
// li{
// list-style:none;
// margin:0.5em 0 0 0; padding:0;
// font-size:0.882em;
// }
// a{
// }
</style>

View File

@ -9,7 +9,7 @@
<script> <script>
import SearchForm from 'vuejs/components/Form/SearchForm' import SearchForm from 'vuejs/components/Form/SearchForm'
import { mapState, mapActions } from 'vuex' import { mapState, mapActions } from 'vuex'
import { MSAPI } from 'vuejs/api/msapi-axios' import { MA } from 'vuejs/api/ma-axios'
export default { export default {
props: ['blockid', 'formhtml'], props: ['blockid', 'formhtml'],
@ -23,17 +23,17 @@ export default {
canSearch: state => state.User.canSearch canSearch: state => state.User.canSearch
}), }),
displayform(){ displayform(){
console.log('computed displayform'); // console.log('computed displayform');
return this.canSearch && this.form return this.canSearch && this.form
} }
}, },
beforeMount() { beforeMount() {
console.log('SearchBlock beforeMount'); // console.log('SearchBlock beforeMount');
this.form = this.formhtml this.form = this.formhtml
}, },
watch: { watch: {
canSearch(new_value, old_value) { canSearch(new_value, old_value) {
console.log('canSearch changed, old: '+old_value+", new: "+new_value); // console.log('canSearch changed, old: '+old_value+", new: "+new_value);
if(new_value && !this.form){ if(new_value && !this.form){
this.getSearchForm() this.getSearchForm()
} }
@ -44,9 +44,9 @@ export default {
}, },
methods: { methods: {
getSearchForm(){ getSearchForm(){
MSAPI.get(`/search_form`) MA.get(`/materio_sapi/search_form`)
.then(({data}) => { .then(({data}) => {
console.log("getSearchForm"); // console.log("getSearchForm");
this.form = data.rendered this.form = data.rendered
}) })
.catch(( error ) => { .catch(( error ) => {

View File

@ -1,23 +1,51 @@
<template lang="html"> <template lang="html">
<UserTools v-if="isloggedin" /> <UserTools v-if="isloggedin" />
<Login v-bind:title="title" v-bind:form="form" v-else /> <LoginBlock v-bind:title="title" v-bind:block="block" v-else/>
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex' import { mapState, mapActions } from 'vuex'
import Login from 'vuejs/components/User/Login' import LoginBlock from 'vuejs/components/Block/LoginBlock'
import UserTools from 'vuejs/components/User/UserTools' import UserTools from 'vuejs/components/User/UserTools'
import { MA } from 'vuejs/api/ma-axios'
export default { export default {
props: ['title', 'form'], props: ['title', 'loginblock'],
data(){
return {
block: null
}
},
computed: { computed: {
...mapState({ ...mapState({
isloggedin: state => state.User.isloggedin isloggedin: state => state.User.isloggedin
}) })
}, },
beforeMount() {
console.log('UserBlock beforeMount');
if(this.loginblock){
this.block = this.loginblock
}else{
this.getLoginBlock()
}
},
methods: {
getLoginBlock(){
MA.get(`/materio_user/login_block`)
.then(({data}) => {
// console.log("getLoginBlock data", data);
this.block = data.rendered
})
.catch(( error ) => {
console.warn('Issue with getLoginBlock', error)
})
}
},
components: { components: {
Login, LoginBlock,
UserTools UserTools
} }
} }

View File

@ -1,16 +1,15 @@
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import { MSAPI } from 'vuejs/api/msapi-axios'
export default { export default {
props: ['form'],
data() { data() {
return { return {
template: null, template: null,
typed: "" typed: ""
} }
}, },
props: ['form'],
methods: { methods: {
keyup() { keyup() {
console.log("search typed", this.typed); console.log("search typed", this.typed);
@ -20,66 +19,29 @@ export default {
} }
}, },
beforeMount() { beforeMount() {
console.log('SearchForm beforeMount'); // console.log('SearchForm beforeMount');
if(this._props.form){ if(this._props.form){
console.log('SearchForm beforeMount if this._props.form ok'); // console.log('SearchForm beforeMount if this._props.form ok');
this.template = Vue.compile(this._props.form).render this.template = Vue.compile(this._props.form)
} // https://github.com/vuejs/vue/issues/9911
}, this.$options.staticRenderFns = [];
beforeUpdate() { this._staticTrees = [];
console.log('SearchForm beforeUpdate'); this.template.staticRenderFns.map(fn => (this.$options.staticRenderFns.push(fn)));
if(this._props.form){
console.log('SearchForm beforeUpdate if this._props.form ok');
this.template = Vue.compile(this._props.form).render
} }
}, },
mounted(){ mounted(){
console.log('SearchForm mounted'); // console.log('SearchForm mounted');
// Drupal.attachBehaviors(this.$el); Drupal.attachBehaviors(this.$el);
},
updated(){
console.log('SearchForm updated');
// Drupal.attachBehaviors(this.$el);
}, },
render(h) { render(h) {
console.log('searchForm render'); // console.log('searchForm render');
if(!this.template){ if(!this.template){
return h('span', 'Loading ...') return h('span', 'Loading ...')
}else{ }else{
return this.template() return this.template.render.call(this)
} }
} }
} }
// searchform: (resolve, reject) => (
// MSAPI.get(`/search_form`)
// .then(({data}) => {
// // console.log("materiosapisearchform", data);
// resolve({
// data() {
// return {
// typed:""
// }
// },
// methods: {
// keyup() {
// console.log("search typed", this.typed);
// },
// submit() {
// console.log("search clicked");
// }
// },
// mounted() {
// Drupal.attachBehaviors(this.$el);
// },
// template: data.rendered
// })
// })
// .catch(( error ) => {
// console.warn('Issue with get searchform', error)
// Promise.reject(error)
// })
//
// )
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>