Uncategorized – Page 2 – Visionmate – En fullservice digital partner

Internationalisering och lokalisering i WordPress

När du utvecklar ett WordPress-tema från scratch så är det en bra ide att internationalisera temat och göra det tillgängligt översättning. Det kommer att låta människor från hela världen använda det på vilket språk de behöver.

Vad är internationalisering (i18n) och lokalisering (l10n)?

I18n är en process som gör ditt tema översättningsbart och det är utvecklarens jobb att få det klart. Den förkortas ofta som i18n eftersom det helt enkelt finns 18 bokstäver mellan i och n.

L10n hänvisar till processen som faktiskt översätter ditt tema till ett annat språk. Det här kan göras av temats användare. För att lokalisera temat måste det först internationaliseras. Lokalisering förkortas som l10n eftersom det finns 10 bokstäver mellan l och n.

Gettext libraries

WordPress använder Gettext libraries för att loada översättningarna. Den har tre typer av filer:

POT (Portable Object Template): Det första steget i översättningsprocessen. Den här filen är ansvarig för att generera strings som behöver översättas. Den innehåller dock inte några översättningar i sig självt.

PO (Portable Object): Dessa filer innehåller däremot översättningarna och ursprungliga strings.

MO (Machine Object): Dessa filer används av funktioner för att loada (ladda) översättningarna. De är viktigast men inte human-readable.

Förbereda temat

Att förbereda ditt tema för internationalisering är inte så komplicerat som det kan tyckas. WordPress tillhandahåller redan en hel uppsättning praktiska funktioner för det. Först och främst måste vi inkludera en funktion i functions.php som kommer att ladda översättningarna:

function my_theme_load_theme_textdomain() {
    load_theme_textdomain( 'my-theme', get_template_directory() . '/languages' );
}
add_action( ‘after_setup_theme','my_theme_load_theme_textdomain' );

Nästa steg är att redigera text strings. För att göra strings översättningsbara bör de inte vara hårdkodade. Wrappa de ursprungliga strängarna i ett av WordPress localization functions. De vanligaste funktionerna:

__()          /* returns the translation of its argument */

_e()          /* echos the string to the browser */

esc_html__()  /* escapes and returns the translation of its argument */

esc_html_e()  /* escapes and echos the translation of its argument */

Exempel:

<h1><?php __( 'Translatable text goes here', 'theme-domain' ); ?></h1>
<h3><?php esc_html__( 'Translatable text goes here', 'theme-domain' ); ?></h3>

Ibland behöver du skjuta ut variabler, datum eller plurals i dina strings. WordPress tillhandahåller lokaliseringsfunktioner för alla dessa fall. Kolla hela listan här.

När dina temafiler är klara så är nästa steg att generera POT-filen. Det finns ett par sätt att generera en POT-fil som beskrivs här. Jag brukar använda Gulp task runner när jag utvecklar WordPress-teman, så jag skapar även POT-fil med hjälp av Gulp (se gulp-wp-pot-paketet).

npm install --save-dev gulp-wp-pot

var gulp = require('gulp');
var wpPot = require('gulp-wp-pot');
 
gulp.task('default', function () {
    return gulp.src('src/*.php')
        .pipe(wpPot( {
            domain: 'domain',
            package: 'Example project'
        } ))
        .pipe(gulp.dest('file.pot'));
});

Pluginen letar efter strings som är insläppta i lokaliseringsfunktionerna och skapar POT-filer med det. POT-filen ska levereras tillsammans med temat så att användarna kan översätta filer med Poedit.

Poedit har öppen källkod och är det mest populära verktyget för att översätta WordPress-teman. Den tar en POT-fil och genererar PO och MO-filer på lämpligt språk.

Nu är ditt tema redo för översättning!

Internationalisering är något du som utvecklare alltid bör göra, det är idag snarare ett måste. Om du planerar att skicka in ditt tema till WordPress.org är det också ett krav.

/ Anastasia

Alla artiklar

Aspektorienterad programmering med Spring AOP över Grails framework

Förord

Aspektorienterad programmering (AOP) är ett nytt koncept och programmeringsparadigm. Det tillåter i princip möjligheten att lägga till funktionalitet till moduler och klasser utan att ändra i koden. Det möjliggörs genom att konfigurera Pointcut, som anger vilka bitar av koden som ska bli executed efter att ha skickat en viss händelse över en metod eller klass.

Mål

Vi strävar efter och antar att utvecklaren, med hjälp av denna artikel, kommer att kunna använda aspektorienterad programmering i Grails-projekt.

Requirements

Grails 3. *

Dependencies:

Spring Aspects 5. *

AspectJ Weaver 1. *

1- Aspektorienterad programmering approach

Oftast innebär våra mjukvarulösningar komplex logik och i detta fall är det särskilt viktigt att hålla en ren och förståelig kod. AOP som tillvägagångssätt hjälper till och möjliggör ett enkelt sätt för modularisering. I grund och botten så tillåter AOP att lägga till funktionalitet till moduler och klasser utan att ändra koden.

2- Bean setting med Spring

Det första steget är att konfigurera Spring AOP, i Spring resource file (conf / spring / resources.groovy). Vi behöver också ange vilken bean som ska fungera som aspect, i vårt fall kommer det att vara Trace bean. Den kan konfigureras på flera sätt, till exempel med annotation @Aspect in din Bean declaration.

I det här fallet måste vi injecta log dependencies som redan finns för Grails, så vi behöver inte skapa några instances själva.

// Aspect-Oriented

xmlns aop:"http://www.springframework.org/schema/aop"

trace(Trace){

    log = log

}

aop{

    config("proxy-target-class": true) {

        aspect(id: "traceService", ref: "trace")

    }

}

3- Implementing logging functionality

Några av de viktigaste begreppen vi behöver ha koll på är: Pointcut, Advice och JoinPoint. En Pointcut är huvudsakligen ett sätt att matcha en method call med en funktionalitet som vi lägger till ett nytt beteende, det behöver ange expression och signature. För att utföra det nya beteendet så behöver vi ange några advice och associera den med en Pointcut.

Advice är declared med annotations som exempelvis @Before och @After och anger när koden ska verkställas. Advice kommer att ta som parameter en JoinPoint, som innehåller information om det aktuella anropet, då menar jag info så som parametervärden, called metod osv.

Testprogrammet är relaterat till dokumenthanteringen. De viktigaste funktionerna är uppladdning, konvertering till HTML och att lagra dokument från olika format. Koden som vi visar ger prov på att vi för track actions över dokumenten.

Här hittar du Aspect bean kallad Trace:

@Aspect

class Trace {

    def log


    @Pointcut("execution(* my.company.myApp.document.DocumentService.add(..))")

    void createDocument(){}



    @Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")

    void updateDocument(Document document, User user){}



    @Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")

    void storeDocument(){}



    @Before("createDocument()")

    void create(JoinPoint jp){

        Document document = jp.args[0] as Document

        User user = jp.args[1] as User

        log.info "The document: ${document?.name} has been created by user: ${user?.username}"

    }



    @Before("updateDocument(document, user)")

    void update(Document document, User user){

        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"

    }

    

    @Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }
}

Jag har tagit upp tre Pointcut sentences, Jag kommer att förklara en del nedan.

Uppdatering av dokument:

@Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")
  
  void updateDocument(Document document, User user){}

I det här fallet anger vi vilken metod vi vill spåra. Vi kan ange arguments i Pointcut declaration, det funkar de flesta gånger.

Vår applikation innehåller method med texten:

void update(Document document, User user){

    Version version = new Version(state: State.UPDATED, user: user, date: new Date())

    document.addToVersions(version)

    document.save()   

}

Sedan efter declaring vår Pointcut så måste vi genomföra den metod som ska utföra required behavior. Vi vill lägga till nytt beteende exakt innan metoden exekveras och sedan spåra vilken användare som har uppdaterat dokumentet.

@Before("updateDocument(document, user)")

    void update(Document document, User user){
      
        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"
    
    }

Lagring av dokument:

I det här nya fallet kommer vi att använda samma endpoint för att fånga ett annat method call:

@Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")
    
    void storeDocument(){}

I vårt system har vi en Repository-klass som ansvarar för att arbeta med vårt files repository, i princip har vi två slags repository, HTML och Source. Vi vill spåra när ett dokument lagras i det.

def addHtmlDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.html

    repository = new HtmlRepository(

            path: repositoryPath,

            converter: new DocumentFormat(format: Format.HTML),    

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}


def addSourceDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.source   

    repository = new SourceRepository(

            path: repositoryPath,

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}

Behavior:

@Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }

Det nya handlingssättet kommer att skriva ut en logg med namnet på called method, arguments och instance description.

4- Slutsats

Aspektorienterad programmering (AOS) är ett nytt koncept, genom att använda det korrekt så ser vi till att vår kod hålls ren. Med AOS kan vi lägga till funktionalitet enklare utan att ändra befintliga klasser och metoder. Det är nödvändigt vara försiktig och hålla en ordentlig dokumentation för att undvika oönskat beteende i vår i vår applikation.

5- Referencer

https://docs.spring.io

Origin: www.linkedin.com

/ Carlos

Senior Web Application Developer på Visionmate

Alla artiklar

Form validation in VueJS

In this article I would like to focus your attention on a JavaScript framework called VueJS, in particular on form validation. I will not go into the details but overall VueJS promotes the idea of minimalism and therefore no validation tools are incorporated in its core. Accordingly, if validation is needed then we include a validation package, if not then the size of our application will be much smaller. At this point let’s finish the introduction and get down to business.

So let’s create a skeleton of the mini-application we will work with. For validation I will use the Vuelidate library. Detailed information about it can be found at: https://monterail.github.io/vuelidate/. Also, for visual decoration I will use Bootstrap 4 styles. A CDN link to bootstrap styles can be found here: https://www.bootstrapcdn.com/ and should be added to the index.html file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-project</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>

Next we install the Vuelidate library with the following command:

npm install vuelidate –save

After the above is completed, we will see the following in the package.json file in the dependencies field:

"dependencies": {
    "vue": "^2.5.17",
    "vuelidate": "^0.7.3"
}

Now to complete the first section we only need to create a component and connect the library. Thus, main.js will look like this:

import Vue from 'vue'
import Vuelidate from 'vuelidate'
import App from './App.vue'

Vue.use(Vuelidate)

new Vue({
  el: '#app',
  render: h => h(App)
})

And the component will look like the below:

<template>
  <div class="container">
  </div>
</template>

<script>
export default {
  data () {
    return {
    }
  }
}
</script>

At this moment, we finish preparation and move on to more interesting stuff 🙂

Next stage is the preparation of the form to be validated. I suggest creating a form with 3 fields (Email, Password and Confirm password). Such forms are commonly used for registration. After updating the template will look like this:

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-6">
        <form class="pt-3" @submit.prevent="onSubmit">
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              id="email"
              class="form-control"
              v-model="email"
            >
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              id="password"
              class="form-control"
              v-model="password"
            >
          </div>
          <div class="form-group">
            <label for="confirm">Confirm password</label>
            <input
              type="password"
              id="confirm"
              class="form-control"
              v-model="confirmPassword"
            >
          </div>
          <button
            class="btn btn-success"
            type="submit"
          >Submit</button>
        </form>
      </div>
    </div>
  </div>
</template>

The new things here are @ submit.prevent = “onSubmit” and v-model = “password”. The first one is the event listener (here it is ‘submit’) and the second one is a so-called two-way binding. Next we need to process all this and right away we can add the necessary validators. I suggest initially using the pre-installed validators that can be found here: https://monterail.github.io/vuelidate/#sub-builtin-validators and later working on the custom ones. But first things first. In order to use the pre-installed validators they should be first imported like this:

import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

I imported only the validators I need now: the required field validation, validation of an email, validation of the minimum string length and validation of the string compatibility. This is also a kind of application optimization since nothing extra is imported. As we have already connected the Vuelidate library earlier, now we can use the “validations” object and validate all the existing fields.

<script>
import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      email: '',
      password: '',
      confirmPassword: ''
    }
  },
  methods: {
    onSubmit () {
      const data = {
        email: this.email,
        password: this.password
      }
      console.log(data)
    }
  },
  validations: {
    email: {
      required,
      email
    },
    password: {
      minLength: minLength(6)
    },
    confirmPassword: {
      sameAs: sameAs('password')
    }
  }
}
</script>

I will not focus now on what is happening in the “validations” object but rather explain how to process all this in the template and show it visually. I will take the email field as an example, still for the other fields the process will be the same. Since the bootstrap styles are available, I will use the “is-invalid” class for the form field and the “invalid-feedback” to display the message text. Next, I will add a wiretap for the “blur” event, which will signal that the validation should be done after the focus removal. After all changes are implemented, the email form group will look like this:

<div class="form-group">
  <label for="email">Email</label>
  <input
    type="email"
    id="email"
    class="form-control"
    :class="{'is-invalid': $v.email.$error}"
    @blur="$v.email.$touch()"
    v-model="email"
  >
  <div class="invalid-feedback" v-if="!$v.email.required">Email field is required</div>
  <div class="invalid-feedback" v-if="!$v.email.email">This field should be an email</div>  
</div>

The most interesting points here are the binded “is-invalid” class activated by any error in the e-mail field, the system variable “$v” which holds the instance to Vuelidate and the directive “v-if” which enables notification display only in case of a certain mistake. This part is now described so we can look at the last one.

Finally, I want to show you custom validation and an easy way to implement it. As an example we will validate the e-mail field for uniqueness. Generally, we would need to contact the server and react on its response. If the server returns information that such email is already in use, then this should be processed and the relevant information should be communicated to the user. However, since it is time consuming to raise the registration server, I will emulate the whole process. Below you can see what I did:

email: {
  required,
  email,
  uniqueEmail: function(newEmail) {
    if (newEmail === '') return true
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const value = newEmail !== 'test@nowhere.com'
        resolve(value)
      }, 1000)
    })
  }
}

I emulated the server behavior with the help of “setTimeout” and if the entered email is the same as the predefined (in my case it is test@nowhere.com), then in 1 second the error will be displayed that this email is already in use. This is of course not the most elegant example, but it is enough to understand how the whole thing works.

In order to summarize everything, let’s first see how the code of the component looks like on the whole.

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-6">
        <form class="pt-3" @submit.prevent="onSubmit">
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              id="email"
              class="form-control"
              :class="{'is-invalid': $v.email.$error}"
              @blur="$v.email.$touch()"
              v-model="email"
            >
            <div class="invalid-feedback" v-if="!$v.email.required">Email field is required</div>
            <div class="invalid-feedback" v-if="!$v.email.email">This field should be an email</div>
            <div class="invalid-feedback" v-if="!$v.email.uniqueEmail">This email is already exists</div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              id="password"
              class="form-control"
              :class="{'is-invalid': $v.password.$error}"
              @blur="$v.password.$touch()"
              v-model="password"
            >
            <div class="invalid-feedback" v-if="!$v.password.minLength">
              Min length of password is {{ $v.password.$params.minLength.min }}. Now it is {{ password.length }}.
            </div>
          </div>
          <div class="form-group">
            <label for="confirm">Confirm password</label>
            <input
              type="password"
              id="confirm"
              class="form-control"
              :class="{'is-invalid': $v.confirmPassword.$error}"
              @blur="$v.confirmPassword.$touch()"
              v-model="confirmPassword"
            >
            <div class="invalid-feedback" v-if="!$v.confirmPassword.sameAs">
              Passwords should match
            </div>
          </div>

          <button
            class="btn btn-success"
            type="submit"
            :disabled="$v.$invalid"
          >Submit</button>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      email: '',
      password: '',
      confirmPassword: ''
    }
  },
  methods: {
    onSubmit () {
      const data = {
        email: this.email,
        password: this.password
      }
      console.log(data)
    }
  },
  validations: {
    email: {
      required,
      email,
      uniqueEmail: function(newEmail) {
        if (newEmail === '') return true
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            const value = newEmail !== 'test@nowhere.com'
            resolve(value)
          }, 1000)
        })
      }
    },
    password: {
      minLength: minLength(6)
    },
    confirmPassword: {
      sameAs: sameAs('password')
    }
  }
}
</script>

And, of course, there is the screenshot below showing how it will be displayed for the user.

As you can see, bootstrap helped a lot in terms of design, and with small amendments this could even be used on a real project. The final conclusion is that forms validation in VueJS is mostly about knowing which library should be used in which case. Alternatively, the universal Vuelidate combine can be used. Vuelidate is for sure not the only library of this kind but it has many advantages: it’s well documented, easy to learn with built-in validators. Additionally it’s very flexible and optimized. Thank you for your attention, please write your thoughts on this subject in the comments and I hope this will be useful for you.

Origin: www.linkedin.com

Alla artiklar

Vi är nu en officiell integrationspartner till Visma

Visionmate är nu en officiell integrationspartner till Visma. Med våra senaste lösningar så kan vi hjälpa er med tillväxt på webben!

Att vara integrationspartner innebär att vi programmerar mot Visma.net på ett säkert och korrekt sätt. Som en del av Vismas certifierade integrationspartner-program erbjuder vi lösningar med färdiga kopplingar till Visma.net och kan integrera säkert och effektivt. För er betyder det att ni kan koppla på nya funktioner till ert Visma-system och på så sätt få en effektivare process.

Kontakta oss gärna för mer information eller se en del av våra lösningar här.

Alla artiklar

Förbättra React forms med Redux Form

Intro

Redux är ett utmärkt verktyg för att dramatiskt förbättra datahanteringen i din applikation. Då borde det vara ett naturligt steg att använda det också för dina application forms. Absolut, men varför återuppfinna hjulet igen?

Redux Form gör exakt det du kan förvänta dig – det använder application state för att hantera form data utan att du behöver skriva all kod själv.

I det här inlägget kommer jag gå igenom processen med att konfigurera Redux-form och ge några exempel på typiska fall.

Setup

Redux Form-inställningen innehåller följande steg:

Inkludera form reducer i combineReducers function (det måste göras en gång per form i din applikation)

import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({  
/* Other reducers */  
    form: formReducer
});
export default rootReducer;

Link selected form to form reducer (låt oss säga att du har en komponent som heter LoginPage)

import {reduxForm} from "redux-form";

export default reduxForm({ 
  form: 'login' /* This must be unique */ 
})(LoginPage);

I själva komponenten måste du länka din form till en funktion som kommer att utföras när du submittar. Detta görs via “handleSubmit” som tillhandahålls av Redux Form.

const {handleSubmit} = props
return (
    <form onSubmit={handleSubmit(login)}>
      <Field
        name='username'
        placeholder = "Enter username"
        component="input"type='text'
      />
      <Field
        name='password'
        placeholder = "Enter password"
        component="input"
        type='password'
      />
      <button type="submit">Login</button>
    </form>
)

Out form innehåller två inmatningsfält (för användarnamn och lösenord) och en knapp.

Inloggningsfunktionen kan vara en komponentfunktion eller en redux-åtgärd – vi behöver bara lägga till den med hjälp av anslutningsfunktionen. Det kommer att användas för asynkron validering.

import {login} from "../actions/actions";

/* If you need to connect your component to redux,
it's best to do it before applying reduxForm HOC*/
LoginPage = connect (mapStateToProps, {login})(LoginPage);

export default reduxForm({
    form: 'login'    
})(LoginPage);

Synkron validering

För att minska belastningen på back-end och förbättra användarupplevelsen är det bäst att utföra synkron validering när det är möjligt – till exempel kan vi kontrollera om användarnamnet och/eller lösenordet innehåller ogiltiga tecken.

Det första steget är att lägga till valideringsfunktionen via reduxForm HOC (higher-order component):

export default reduxForm({
    form: 'login',
    validate
})(LoginPage);

Och lägg till själva valideringsfunktionen ovanför LoginPage container.

const validate = (values) => {
    const errors = {}
    if(!values.username) {
        errors.username = "Cannot be empty"
    }
    if(!values.password) {
        errors.password = "Cannot be empty"
    }
    return errors
}

När vi ändrar användarnamn eller lösenord, kommer den synkrona valideringen att köras och kontrollera om båda fälten är populated. Om en valideringsfunktion returnerar errors så kommer formuläret inte att skickas in.

Asynkron validering

Om form values framgångsrikt överför synkroniserad validering så skickas de vanligtvis till en server via ett asynkront anrop. Med hjälp av Redux-formuläret kan vi inaktivera formuläret tills vår begäran har behandlats och ett felmeddelande visas om det inte lyckades.

handleSubmit kommer utföra vår inloggningsåtgärd:

export function login(values) {    
    return (dispatch) => {        
        return ApiConnector.login(values.username, values.password)
            .then(response => {
                if(response.data.success === true){
                  dispatch({
                    type: LOGIN
                  })
                } else {
                  throw new SubmissionError({        
                    _error: "Login failed!"
                    })

                }
            }) 
            .catch((error) => {              
              throw new SubmissionError({        
                _error: "Login failed!"
              })
            })
    }
}

Eftersom denna åtgärd använder ett asynkront anrop, måste vi använda Redux Thunk middleware. Funktionen ApiConnector.login är en post request som skickas med hjälp av axios. För att asynkron validering ska fungera korrekt är det viktigt att returnera Promise-objektet.

Vi kan se att om förfrågan till servern misslyckas (till exempel om en server är nere) eller inloggningen misslyckas, då returneras ett felmeddelande tillbaka till vårt formulär.

Hur kan vi visa felmeddelandet? Vi ändrar vår form container för att extrahera felet från props och visa den ovanför knappen “Logga in”.

const {handleSubmit, error} = props
return (
  <form onSubmit={handleSubmit(login)}>
    <Field
      name='username'
      placeholder = "Enter username"
      component="input"
      type='text'
    />
    <Field
      name='password'
      placeholder = "Enter password"
      component="input"
      type='password'
    />
    {error && <strong>{error}</strong>}
    <button type="submit">Login</button>
  </form>
)

Initial values

Många gånger vill du att dina forms ska ha några initiala värden (möjligen baserat på tidigare användarval). Detta kan göras genom att lägga till initialValues-objektet till container props (till exempel genom mapStateToProps).

function mapStateToProps(state) {
  return {
    initialValues: {
      username: "admin"
    }
  }
}

Vanligtvis kommer initialvärden att fyllas på från application state.

Changing values programmatically

Det finns situationer, när ett värde ändras, eller du automatiskt vill ändra ett annat värde – till exempel om någon vill logga in med “gäst” användarnamn, så ändras lösenordet automatiskt till “gäst” också.

Först måste funktionen “ChangeChange” läggas till med reduxForm HOC.

export default reduxForm({
    form: 'login',
    validate,
    onChange
})(LoginPage);

onChange-funktionen kan läggas till ovanför Login Page komponenten.

import {reduxForm, change} from "redux-form";

const onChange = (values, dispatch, props, previousValues) => {
  if(values.username === "guest") {
    dispatch( change ('login', 'password', 'guest');
  }
}

Detta kan vara väl användbart om du har ett värde som är helt beroende av ett annat.

Sammanfattning

Genom att använda informationen i det här inlägget så bör du kunna implementera Redux Form i dina applikationer. Det är ett mycket kraftfullt verktyg, som i stort sett innehåller all den funktionalitet som behövs för hantering av application forms.

Om du behöver mer information så är det bästa stället att hitta det på här: www.redux-form.com

/ Radek

Origin: www.linkedin.com

Alla artiklar

Animation i React med React Transition Group

Häromdagen jobbade jag på ett nytt projekt utvecklat i React. En del av mitt jobb var att översätta statisk HTML till interaktiva React-components. Jag ville liva upp applikationen lite genom att använda animering.

Under lång tid sköttes det hela perfekt av jQuery. Men med uppkomsten av nya JavaScript-frames, som React och Angular, så försvann möjligheterna med jQuery. DOM-manipulation i React fungerar annorlunda. Komponenterna har livscykler som kan hoppa in och ut ur DOM.

Efter lite research kom jag över några animeringsbibliotek som exempelvis React Transition Group, React-spring, React-motion och andra. Vart och ett av dessa bibliotek har sina för- och nackdelar, några av dem var helt enkelt bara för krångliga att använda. Allt beror dock på projektets behov. Jag ville lägga till snygga bild- och övergångseffekter till vissa element. Det är egentligen en ganska enkel animering så jag bestämde mig för att välja React Transition Group  just på grund av dess enkelhet.

React Transition Group brukade vara en React add-on. Nu är det separerat och underhålls av communityn.

Jag vill nämna att React Transition Group-komponenterna inte ger oss själva animationen. I stället ger det oss en state för varje lifecycle så att vi kan tillämpa CSS-animering på komponenten när den ändrar state.

Låt oss ta en titt på hur det fungerar i praktiken. Jag har skapat en enkel sidebar. Den är gömd som standard och triggas av en knapp. När knappen klickas kommer komponenten att ändra state och producera en side bar.

import React, { Component } from "react";
import { render } from "react-dom";


class App extends Component {
//Initial state
 state = {
  show: false
 };

//Toggle show/hide sidebar
 toggleShow() {
  this.setState({
   show: !this.state.show
  });
 }

//Rendering sidebar
renderSidebar() {
 if(this.state.show) {
  return (
   <div
    style={{
     width: "50%",
      height: "100vh",
      paddingTop: '10px',
      background: "green",
      color: "white",
      textAlign: "center",
      transition: "all 1s ease",
      position: "absolute",
      top: 0,
      left: 0
     }}
    >
     Hi, there!
    </div>
   )
  } else {
    return null
   }
  }


 render() {
  return (
   <React.Fragment>
    {this.renderSidebar()}

    <button
     onClick={() => this.toggleShow()}
     style={{float: "right"}}>
      Button
    </button>

   </React.Fragment>
  );
 }
}


render(<App />, document.getElementById("root"));

Nu har vi fått igång det. Vi lägger till en fin slide effect när side bar dyker upp. Vi använder <Transition /> component. Låt oss först installera paketet och importera <Transition /> component till vår App.

npm install react-transition-group --save

import Transition from "react-transition-group/Transition";

Komponenten <Transition /> component kan finnas i fyra tillstånd: “Enter”, “entered”, “exiting” och “exited”. Det låter oss se dess beteende när det passerar från ett state till ett annat. Det tar också två huvudsakliga props: “timeout” och “in”.

I “timeout” -protokollet ställer vi in övergången i millisekunder. “In” -prop kommer att spåra staten. När “in” -värdet är lika med “true” flyttas en komponent till “in” -state och stannar där i 500ms innan man växlar till “inmatat” tillstånd. Samma process händer när “in” -värdet är lika med “false” – först flyttas en komponent till “exiting” och sedan till “exited” -state.

En annan viktig detalj jag vill nämna är att <Transition /> component returnerar en funktion med state som ett argument. Att placera children elements i det kommer inte att fungera.

Här är slutkoden.

import React, { Component } from "react";
import { render } from "react-dom";

//Import Transition component
import Transition from "react-transition-group/Transition";


class App extends Component {
//Initial state 
state = {
  show: false
 };

//Toggle show/hide sidebar
 toggleShow() {
  this.setState({
   show: !this.state.show
  });
 }


//Rendering sidebar
renderSidebar() {
 return (
  <Transition in={this.state.show} timeout={1000}>
   {state => (
    <div
     style={{
      width: "50%",
      height: "100vh",
      paddingTop: "10px",
      background: "green",
      color: "white",
      textAlign: "center",
      transition: "all 1s ease",
      position: "absolute",
      top: 0,
      //This is where all the magic happens :)
      left: state === "entering" || state === "entered" ? 0 : "-50%"
     }}
    >
     Hi, there!
    </div>
   )}
  </Transition>
 )
}


render() {
 return (
  <React.Fragment>
   {this.renderSidebar()}

   <button
    onClick={() => this.toggleShow()}
    style={{ float: "right" }} >
     Button
   </button>

  </React.Fragment>
  );
 }
}


render(<App />, document.getElementById("root"));

Som du kan se har jag justerat “renderSidebar ()” -funktionen. Jag har wrappat sidofältet med <Transition/> komponenten där “in” -prop lagrar staten och “timeout” är inställd på 1000ms. Allt det magiska sker på CSS ” “left” property. När state är lika med “inmatning” eller “inmatad” så tar den 0 och komponenten blir synlig. Annars flyttas den bort med -50 % (i förhållande till dess bredd) och blir osynlig.

Så här fungerar React Transition Group. Vi gör animationen med hjälp av CSS. Självfallet kan vi göra mycket mer med det. React Transition Group ger oss också andra komponenter så som <CSSTransition /> och <TransitionGroup /> där vi kan lägga till egna namn eller animera element i en lista.

Sammanfattningsvis vill jag säga att React Transition Group är ett utmärkt val för enkla animeringar.

/ Anastasia

Origin: www.linkedin.com

Alla artiklar

Kontinuerlig integration och Kontinuerlig Deployment med Jenkins Pipeline i Grails projects

Inledning

Programutveckling är spännande, men det är också ett yrke där du ständigt behöver utveckla och bredda din kompetens och ofta tänka utanför boxen. Förut hade mjukvaruföretagen bekymmer med att utveckla och leverera programvara snabbt och med bibehållen kvalitet. Det ledde så klart till att en stor andel projekt misslyckades. Sakta men säkert började man dock komma på metoder och processer för att upprätthålla flödet av s.k. hållbar utveckling.

För att vara konkurrenskraftig nu och/eller inom en snar framtid är det viktigt att möta efterfrågan på högkvalitativ mjukvara och implementera teknik som gör det möjligt för verksamheten att göra jobbet. Det finns många verktyg som kan hjälpa företaget att utveckla sina processer och hålla snabba kvalitativa lanseringar. Jag kommer i det här inlägget berätta om det kontinuerliga integrationsverktyget Jenkins, vilket är ett open source-verktyg som har många nyttiga och användbara tillägg. Jag kommer att visa hur man skapar en pipeline (Jenkins Declarative Pipeline) som ger en flexibel och kraftfull metod för att bygga, testa och deploya programvaror.

Mål

Skaffa allmän kunskap om kontinuerliga integrationsverktyg i mjukvaruprocessen, kunna automatisera integreringen och deployment av software pieces.

Systemkrav

Jenkins 2.73.3 (Om du använder Linux OS behöver du byta Batch skriptet mot Shell skriptet)

Grails 3. *

1.0. Kontinuerlig integration och Deployment strategi

Kontinuerlig integration är en mjukvaruutvecklingspraxis där medlemmar i ett team ofta samordnar sitt arbete. Vanligtvis integrerar varje person åtminstone på daglig basis, vilket leder till flera integreringar per dag. Varje integrering verifieras automatiskt (inklusive test) för att upptäcka eventuella integrationsfel så snabbt som möjligt (Martin Fowler, 2006).

Utvecklarna bör fokusera på utveckling och inte på implementeringsprocessen. Utvecklarens roll är att leverera kvalitetskod och programvara, när koden väl finns på plats så är utvecklaren inte ansvarig för den mer. Det låter ju bra tänker du, men hur löser man det? Det är inte en bra lösning att göra det manuellt, anledningen är att du då måste skapa en lokal miljö för att bygga och deploya mjukvaran. Om programvaran visar sig vara mer komplex så blir också byggnads- och installationsprocessen mer komplicerad. Det bästa sättet är därför att automatisera hela processen, i det här fallet kan en DevOps-ingenjör i ditt team med all säkerhet lösa processen. När koden väl finns på plats kommer den automatiskt att kompileras och deployas i alla olika miljöer med hjälp av varierande tekniker och verktyg. Utvecklarna kan då koncentrera sig på just utvecklingen.

2.0. Jenkins

Jenkins är ett välkänt verktyg för kontinuerlig integration. Verktyget har öppen källkod och gratis, det har även ett ganska stort utvecklar-Community. Med Jenkins kan du bygga ditt projekt, testa och deploya det i dina olika miljöer.

I verktyget är det möjligt att skapa olika typer av jobb eller objekt, bland dem kan du hitta “Freestyle-projektet” där du kan använda olika moduler för att bygga och deploya din programvara. “Jenkins pipeline” är ett mycket enkelt sätt att dra fördel av alla egenskaper hos Jenkins. Jenkins pipeline är dessutom mer flexibelt och anpassningsbart. I det här inlägget vill jag visa ett exempel på det.

2.1. Jenkins pipeline

Jenkins pipeline är ett Groovy-skript (Groovy är ett dynamiskt och modernt språk som översätts av Java Virtual Machine (JVM)). Pipen kan vara deklarativ eller scriptad, skillnaden är att deklarativ måste följa en viss syntax och den scriptade bör inte göra det. I det här fallet kommer vi att använda Declarative Pipeline. Declarative Pipeline har principiellt en enkel och användbar struktur där du kan dela upp ditt arbete steg för steg.

Nedan ser du en deklarativ Pipeline-struktur:

pipeline {
    agent any 
    stages {
        stage('Stage name') {
            steps{ 
            }
        } 
    }
}

Agent syntax specificerar var och hur pipen körs. Du kan lägga in många faser och var och en innehåller olika steg. I stegsyntaxen beskriver du den åtgärd du vill göra. Den deklarativa pipen är bred och innehåller många användbara syntaxer (se: https://jenkins.io/doc/book/pipeline).

Pipen kan implementeras i ett Jenkins-jobb eller som en Jenkinsfil i källan till ditt projekt, den senare är bara en fil som innehåller din deklarativa pipeline (se: https://jenkins.io/doc/book/pipeline/jenkinsfile/ ). Vidare kommer vi att använda Pipeline inom ett Jenkins-jobb.

2.1.1. Uppdatera från version control repository

I detta skede uppdaterar vi källkoden. Vi använder en Git-modul för att hantera den, den finns installerad som standard. Vi behöver bara ange vilken filial vi ska använda, webbadressen till repository och åtkomst-id. Vi kan konfigurera vår access-id i avsnittet “Credentials”.

stage(‘Pull’) {

    steps{
        git(branch: 'myBranch', 
            credentialsId: 'myId', url: 'https://github.com/myCompany/MyProject')
    }
}

2.1.2. Bygg ditt projekt

I det här skedet bygger vi projektet genom att köra ett Grails-kommando med batch modul. Genom att använd Grails i projektet så behöver vi bara köra ett kommando för att bygga det och ange var vi ska skapa vår .war-fil.

Senare måste vi arkivera vår artefakt för att få den tillgänglig för deployment eller något annat, vi använder wildcards för att ange sökvägen till artefakten.

stage('Build') {
    steps{
        bat(script: 'grails war', returnStatus: true)
        archiveArtifacts '**/build/libs/*.war'
    }
}

2.1.3. Testa ditt projekt

Grails 3. * använder Gradle som verktyg och med andra instrument, som exempelvis Ant och Maven, kan vi implementera tasks genom att anropa dem direkt eller i olika steg. När vår .war artefakt skapas kan vi testa vår mjukvara automatiskt. I det här fallet kan du köra ditt test i byggprocessen. I det här inlägget testar vi efter att ha byggt vårt projekt med en ny fas i Jenkins Pipeline.

stage('Test') { 
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'grails test-app my.package.MyTestClass.myTestMethod | findstr BUILD')
            
            if(status.contains('BUILD') && status.contains('FAILED'))
                return true
               else 
                return false
        }
    }
    steps{
        error("TEST FAILED")
    }

}

I detta steg använde vi ett villkorat steg för att göra hela processen misslyckad om testet misslyckades.

2.1.4. Deploy

I det här fallet har vi Jenkins körande på samma server som vår applikationsserver och det är tillräckligt för att kopiera vår artefakt.

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

Med “selector: specific (‘$ {BUILD_NUMBER}’)” kommer vi att vara säkra på att kunna deploya vår artefakt (som genererats av det aktuella bygget).

2.2. Hook triggers

I avsnittet “Build Triggers” kan du se över “GitHub hook trigger för GITScm polling” options och i dina Github projektinställningar måste du lägga till en ny webhook med en hemlig nyckel som du behöver konfigurera även i Jenkins. Efter det, när Github upptäcker någon förändring i din filial, skickar den en notification till Jenkins som startar upp ditt jobb.

2.3. Ytterligare konfigurationer och tips

Om du använder Tomcat och får problem med att automatiskt deploya .war-filer så kan du lägga till nya steg i din pipeline:

stage('PreDeploy') {
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'sc query Tomcat8 | findstr RUNNING || echo STOPPED')
           
            if(status.contains('STATE') && status.contains('RUNNING'))
                return true
               else 
                return false
        }
    }
    steps {
        bat(script:'net stop Tomcat8')
    } 
}

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

stage('PostDeploy') {
    steps {
        bat(script:'net start Tomcat8')
    }
}

Sammanfattning

Den tid du lägger på att förbättra utvecklingsprocessen, till exempel för att genomföra kontinuerlig integration och kontinuerlig deployment, är aldrig bortkastad och det kommer att säkerställa snabbare utgåvor, också ett fokus på utvecklingsprocesser och naturligtvis en nöjd kund. Jenkins pipeline erbjuder oss ett kraftfullt och enkelt sätt att göra det.

 / Carlos

 

Kommentera eller fråga gärna på mail:

info(@)visionmate.se

 

Referenser

Martin Fowler, 2006: Continuous Integration, https://www.martinfowler.com/articles/continuousIntegration.html

Alla artiklar

Hantera säkerhet i React med JWT

Under de senaste åren har det skett en stor förändring kring sättet vi använder programvara. Fokus har skiftats från stationära applikationer till lättdrivna applikationer i molnet (SaaS). Välkända företag som Facebook och Google har varit ledande i utvecklingen kring denna teknik.

Google har som exempel introducerat Angular, medan Facebook ligger bakom React, båda JavaScript bibliotek som används för att bygga webbgränssnitt.

På Visionmate har vi valt den senare alltså React för att bygga våra applikationer. I det här inlägget tänkte jag prata mer om hur vi hanterar själva säkerheten i applikationsutvecklingen.

JSON Web Token (JWT) är ett populärt sätt för att säkra upp och skydda dina processer (finns detaljerat i RFC 7519-standard).

Den stora fördelen med att använda en JWT-token (säkerhetsnyckel) är att du kan skjuta in extra information som bara kan avkodas av din back-end-kod.

Vi använder vi Grails för back-end och använder Spring Security REST-plugin för att generera och hantera JWT-tokens. Jag kommer inte gå in så mycket på just det idag, men jag eller någon annan i teamet planerar att skriva mer om ämnet framöver.

I det här exemplet använder jag Redux som state container inkluderat autentiseringsdata och nyckelinformation.

Inloggningsfunktionen skickar inte bara uppgifter om själva säkerhetsnyckeln utan även tilldelade användarroller. Anropet talar om alltså vilken typ av rättigheter användaren har – till exempel om de kan redigera vissa dokument eller få tillgång till adminpanelen.

API-inloggningsfunktionen (alla anrop görs med axios):

function login(user) {
        const config = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return axios.post(`${SERVER_URL}/api/login`, user, config)       
    }

Login action:

export function login(username, password) {
    const user = {
        username,
        password
    };
    return (dispatch) => {
        ApiConnector.login(user)
            .then(response => {
                const data = response.data
                const user = {
                    access_token: data.access_token,
                    refresh_token: data.refresh_token,
                    roles: data.roles,
                    username: data.username,
                };
                sessionStorage.setItem('user', JSON.stringify(user));
                dispatch({
                    type: LOGIN,
                    user
                });
            })
            .catch((error) => {
                console.log(error)
            })
    }
}

Eftersom vi har en asynkron kommunikation så används redux-thunk middleware för att hantera den. Som du kan se innehåller svaret inte bara åtkomst- och uppdateringstoken, men även användarroller som senare kan användas för att till exempel göra ytterligare paneler på ett dashboard.

Nyckeln lagras sedan i antingen en session- eller lokal lagring beroende på applikationskraven, det här gör att användaren inte behöver logga in varje gång en webbläsare uppdateras.

Autentiseringsdatan lagras i en reducer:

export default function (state = {loggedIn: false}, action) {
    switch(action.type) {        
        case LOGIN:
            return {...state,
                user: action.user,                 
                loggedIn: true                
            };
        case LOGOUT:
            return {...state,
                user: {},
                loggedIn: false
            };
        default:
            return state
    }
}

En JWT-nyckel inkluderas i varje anrop. Därför kan den lagras i en separat JavaScript-fil som enkelt hämtas senare.

I det här fallet lagras den i headers.js:

export default () => {
    return {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${sessionStorage.getItem('user') ? JSON.parse(sessionStorage.getItem('user')).access_token : null}`
    }
}

Funktionen adderas sedan till varje API-anrop:

import headers from "../security/headers";

 function get(target, params) {
        const config = {
            headers: headers(),
            params
        }
        return axios.get(`${SERVER_URL}/api/${target}`, config)
    }

När säkerhetsnyckeln löper ut och blir ogiltigt så behöver användaren inte logga in igen – det är tillräckligt att använda uppdateringen för att få en ny giltig access.

Uppdatera token function:

function refreshToken() {
        const config = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        };
        const body = {
            grant_type: "refresh_token",
            refresh_token: JSON.parse(sessionStorage.user).refresh_token
        };
        axios.post(`${SERVER_URL}/oauth/access_token`, qs.stringify(body), config)
            .then(response => {
                const data = response.data
                const user = {
                    access_token: data.access_token,
                    refresh_token: data.refresh_token,
                    roles: data.roles,
                    username: data.username                    
                };
                sessionStorage.setItem('user', JSON.stringify(user));
                store.dispatch({
                    type: REFRESH_TOKEN,
                    user
                })
            })
    }

För att logga ut användaren, radera helt enkelt nyckeln från session/lokal lagring som visas i åtgärden nedan.

Logout action:

export function logout() {
    sessionStorage.removeItem('user');
    return {
        type: LOGOUT
    }
}

Summering

Som jag försökt visa i det här inlägget så måste vissa steg följas för att kunna använda JWT med din applikation. Du kan också använda exemplet som en mall för att lägga till mer funktionalitet och på så sätt utveckla din applikation.

/Radek

 

Bibliotek som används: redux, redux-thunk och axios.

Skicka gärna frågor och kommentarer till vår mail:

info(@)visionmate.se

 

Alla artiklar