Monthly Archiv: August, 2021

PHP Search Analytics Utilities (New)

Package:
PHP Search Analytics Utilities
Summary:
Find keywords to optimize pages for search engines
Groups:
PHP 5, Searching, SEO, Web services
Author:
Juraj Puchký
Description:
This package can find keywords to optimize pages for search engines...

Read more at https://www.phpclasses.org/package/12178-PHP-Find-keywords-to-optimize-pages-for-search-engines.html

6 Advanced JavaScript Concepts You Should Know

6 Advanced JavaScript Techniques You Should Know

There have been a number of articles published over the years that discuss best practices techniques for JavaScript. I thought I would go a little bit beyond the scope of those articles and outline a number of advanced JavaScript concepts and techniques that I have personally used or read about that could be invaluable in certain circumstances.

This article doesn’t necessarily cover every detail of the methods I’m describing, but provides an overview, along with advanced JavaScript examples with code.

1. Closures to Extend Variable Scope

Closures in JavaScript are a fairly straightforward concept, and have been discussed online in a number of in-depth articles. The fact that they are straightforward doesn’t necessarily mean they’re simple however, as seen by the extensive articles that cover the subject.

Simply put, closures allow variable scope to be extended past the common scope restrictions of functions. I like the way Jeremy Keith describes closures in his book Bulletproof Ajax:

“Think of closures as a kind of regional scope: broader than local but not as broad as global.”

To create a closure, you nest a function inside of a function. That inner function has access to all variables in its parent function’s scope. This comes in handy when creating methods and properties in object oriented scripts. Here is a simple example that demonstrates the use of a closure:

function myObject() {
  this.property1 = "value1";
  this.property2 = "value2";
  var newValue = this.property1;
  this.performMethod = function() {
    myMethodValue = newValue;
    return myMethodValue;  	
  };
  }
  var myObjectInstance = new myObject();  
  alert(myObjectInstance.performMethod());

The key portions of the script are the nested anonymous function are highlighted in green and the method call in the alert function (last line). Because the method in the alert is actually calling a nested function, that method is able to read the value of the variable called newValue, even thought that variable is not within the scope of the anonymous function, or method.

Developers use closures all the time, probably unknowingly, since a closure is created any time an anonymous function is nested inside another function and utilizes variables from the parent function’s scope. The power of the closure is revealed when that method (the inner function) is called, and values that normally wouldn’t be accessible are within “regional” scope and are thus able to be used as any other value.

See the references below for some deeper explanations of closures and their relation to scope. I also highly recommend you pick up a good advanced JavaScript book that offers a good discussion of the concepts associated with closures.

Further Reading

  • Explaining JavaScript scope and closures (Robert Nyman)
  • Closures in JavaScript (James Padolsey)
  • JavasCript Closures at Jibbering.com
  • JavaScript Closures for Dummies

2. Object Literals to Pass Optional Arguments

Here is a handy coding tip to keep in mind when dealing with functions that can accept a large number of optional arguments. Instead of passing the large number of arguments in the conventional fashion, which could unnecessarily complicate the function, you can pass just one argument which ends up being a collection of arguments declared in an object literal.

Let’s look, first of all, at how we might do this in the typical manner, so we can see the contrast:

function showStatistics(name, team, position, average, homeruns, rbi) {
  document.write("<p><strong>Name:</strong> " + arguments[0] + "<br />");
  document.write("<strong>Team:</strong> " + arguments[1] + "<br />");

  if (typeof arguments[2] === "string") {
    document.write("<strong>Position:</strong> " + position + "<br />"); 
  }
  if (typeof arguments[3] === "number") {
    document.write("<strong>Batting Average:</strong> " + average + "<br />");
  }
  if (typeof arguments[4] === "number") {
    document.write("<strong>Home Runs:</strong> " + homeruns + "<br />");
  }
  if (typeof arguments[5] === "number") {
    document.write("<strong>Runs Batted In:</strong> " + rbi + "</p>"); 
  }
}
showStatistics("Mark Teixeira");
showStatistics("Mark Teixeira", "New York Yankees");
showStatistics("Mark Teixeira", "New York Yankees", "1st Base", .284, 32, 101);

The function above can take up to 6 arguments. The first two arguments are mandatory, so inside the function, we don’t check for their existence. The last 4 arguments are not mandatory, so we only display their values if they exist.

We call the function 3 different times (last 3 lines), with different numbers of arguments each time. You can see that if the number of passed arguments was in the dozens, or more, the code could look a little messy, and would be harder to maintain, or read.

Now let’s look at the same code using object literals to pass the arguments:

function showStatistics(args) {
  document.write("<p><strong>Name:</strong> " + args.name + "<br />");
  document.write("<strong>Team:</strong> " + args.team + "<br />");
  if (typeof args.position === "string") {
    document.write("<strong>Position:</strong> " + args.position + "<br />"); 
  }
  if (typeof args.average === "number") {
    document.write("<strong>Average:</strong> " + args.average + "<br />");
  }
  if (typeof args.homeruns === "number") {
    document.write("<strong>Home Runs:</strong> " + args.homeruns + "<br />");
  }
  if (typeof args.rbi === "number") {
    document.write("<strong>Runs Batted In:</strong> " + args.rbi + "</p>");
  }
}

showStatistics({
  name: "Mark Teixeira"
});

showStatistics({
  name: "Mark Teixeira",
  team: "New York Yankees"
});

showStatistics({
  name: "Mark Teixeira",
  team: "New York Yankees",
  position: "1st Base",
  average: .284,
  homeruns: 32,
  rbi: 101
});

Technically, this second method of passing the arguments might require a little bit more code, but with a large collection of arguments, there are a few advantages.

First, the function itself is simplified because it accepts only one argument (args), which is a collection of all the values passed from the object literal (name, team, position, etc). Plus, the actual argument values are easy to read, and can easily be understood, updated, or modified, since the correlation between the values and the argument references are more direct.

If the function required only a small number of arguments, then this method would not be necessary, and might actually have the opposite effect. So, use this technique sparingly, and only in situations where you foresee the collection of arguments being hard to maintain over time.

Further Reading

3. Contextual Targeting of DOM Elements

There are sometimes instances where you need to traverse the DOM and gain access to a specific element, or group of elements, but due to certain restrictions, you may not have direct access to the elements via a CSS class name or ID in the HTML code. This might be because of user-generated content produced through a rich text editor, or dynamic content pulled from a database.

Whatever the case, it’s not impossible to access those unidentified DOM elements via JavaScript. Using what I call “contextual targeting”, you can gain access to, and modify, almost any element in the DOM. As long as you have a map of the general template that contains the element you want to target, you can access that element and manipulate it the same way you would an element that has a class name or ID.

Let’s create some basic HTML code that will serve as our example page:

<div id="header">
  <h1>Site Title</h1>
</div>
<div id="sidebar">
  <ul>
    <li><a href="#">Testing</a></li>
    <li><a href="#">Testing</a></li>
    <li><a href="#">Testing</a></li>
    <li><a href="#">Testing</a></li>
    <li><a href="#">Testing</a></li>
    <li><a href="#">Testing</a></li>
  </ul>
</div>
<div id="content">
  <h2>Page Title</h2>
  <p><a href="#">Lorum Ipsum link here</a>. Pellentesque habitant morbi
     tristique senectus et netus et malesuada fames ac turpis egestas.
     Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, 
     ante. Donec eu libero sit amet quam egestas semper.
     Aenean ultricies mi vitae est. Mauris placerat eleifend leo.
     Pellentesque habitant morbi tristique senectus et netus et malesuada
     fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
     ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
     egestas semper. Aenean ultricies mi vitae est. Mauris
     placerat eleifend leo.</p>
  <p><span style="color: red;">Pellentesque habitant morbi</span>
    tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum
    tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec
    eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est.
    Mauris placerat eleifend leo. Pellentesque habitant morbi tristique senectus
    et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam,
    feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit
    amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat
    eleifend leo.</p>    
</div>
<div id="footer">
   <p>Copyright | <a href="#">contact</a> | <a href="#">policy</a> | 
      <a href="#">privacy</a></p>
</div>

Using the HTML code above, if we wanted to target all the anchor tags on the page, we could collect them and manipulate them like this:

var myLinkCollection = document.getElementsByTagName("a");
  
for (i=0;i<myLinkCollection.length;i++) {
  // do something with the anchor tags here
}

If we wanted to target only the anchor tags in the footer, however, we would target them based on their context, or surrounding elements, like this:

var myFooterElement = document.getElementById("footer");
var myLinksInFooter = myFooterElement.getElementsByTagName("a");
for (i=0;i<myLinksInFooter.length;i++) {
  // do something with footer anchor tags here
}

The first line grabs a reference to the footer element. The second line collects all <a> tags inside the footer. Then we loop through them and do what we want with them. Thus, they are accessible even though they are not grouped via class names.

You can accomplish the same thing by using node properties, as shown below.

var myLinkCollection = document.getElementsByTagName("a");

for (i=0;i<myLinkCollection.length;i++) {
  if (myLinkCollection[i].parentNode.parentNode.id === "footer") {
    // do something with footer anchor tags here
  }
}

Similar code could be used to target the lone anchor tag inside the “content” section.

We could also limit our anchor tag search to include only tags that have the href attribute set, so as to avoid finding any in-page links. We do this by using the getAttribute method:

var myLinkCollection = document.getElementsByTagName("a");

for (i=0;i<myLinkCollection.length;i++) {
  if (myLinkCollection[i].getAttribute("href")) {
    // do something with the anchor tags here
  }
}

Finally, you’ll notice that there is a <span> tag with an inline style. The inline style could have been generated through a content management system, so you may not have the ability to edit it directly. You can target all <span> elements with inline styles like this:

var myLinkCollection = document.getElementsByTagName("span");

for (i=0;i<myLinkCollection.length;i++) {
  if (myLinkCollection[i].getAttribute("style")) {
    // do something with all anchors that have inline styles
  }
}

The possibilities are endless with contextual targeting, and there are even more options available if you’re using a JavaScript library that normalizes browser differences and simplifies DOM manipulation.

Further Reading:

4. Using Namespaces to Prevent Conflicts

If you’re doing an extensive amount of raw JavaScript coding and suspect that additions could be made to the same pages you’re working on, you can prevent any future conflicts with your code by giving your code its own namespace.

Object-oriented JavaScript implements namespace-like principles due to the fact that properties and methods are declared inside of objects, thus there are less likely to be conflicts. A conflict could arise, however, through object names. And very likely, the conflict will occur “silently”, thus you may not be alerted to the issue immediately.

You can prevent all conflicts by creating a unique namespace. Let’s use the showStatistics function to demonstrate how we can encapsulate code into its own namespace:

if (typeof MY == "undefined") {
  MY = new Object();
  MY.CUSTOM = new Object();
}

MY.CUSTOM.namespace = function() {
  function showStatistics(args) {
    document.write("<p><strong>Name:</strong> " + args.name + "<br />");
    document.write("<strong>Team:</strong> " + args.team + "<br />");
    if (typeof args.position === "string") {
      document.write("<strong>Position:</strong> " + args.position + "<br />");
    }
    if (typeof args.average === "number") {
      document.write("<strong>Average:</strong> " + args.average + "<br />");
    }
    if (typeof args.homeruns === "number") {
      document.write("<strong>Home Runs:</strong> " + args.homeruns + "<br />");
    }
    if (typeof args.rbi === "number") {
      document.write("<strong>Runs Batted In:</strong> " + args.rbi + "</p>");
    }
  }

  showStatistics({
    name: "Mark Teixeira",
    team: "New York Yankees",
    position: "1st Base",
    average: .284,
    homeruns: 32,
    rbi: 101
  });
}
MY.CUSTOM.namespace();

The first few lines create the namespace by checking to see if the “MY” object already exists. This object can be whatever you want it to be. Just pick a name that you don’t think will ever be used again. After the MY object is created, we are then able to create the “CUSTOM” object as a property of the MY object. Then our namespace function becomes a method of the MY.CUSTOM object. Keep in mind that “MY“, “CUSTOM” and “namespace” can each be your own custom names. I chose these for demonstration purposes. They could be CHEESEBURGER.ONIONS.pickles if you want!

The showStatistics function is exactly the same as in the example earlier that utilizes an object literal to pass in the values. But in this case, the entire function, including the object literal, is encapsulated inside my.custom.namespace. The last line invokes the entire function using dot notation, and the function runs exactly the same as it normally would, except that it is protected from conflicting with another function called “showStatistics“.

Further Reading:

5. Hybrid Application Development

You can create powerful JavaScript applications if you use a combination of a JavaScript library and raw JavaScript code. Many JavaScript libraries are used to implement “pretty” animations and other customizable effects–sometimes via plugins– that often don’t require much to be added to them other than some custom values.

On the other hand, there may be situations where you’ll want to accomplish something specificly requested by a client. Maybe it’s something not available in a library and that requires extensive coding, possibly utilizing Ajax and a variety of DOM methods.

There is no point in reinventing the wheel. You can implement your favorite JavaScript library and take advantage of its simplified Ajax calls, DOM methods, and normalization of browser differences. Thus, you can have the advantages of the library, while still creating custom scripts that are specific to your project.

Further Reading:

6. Rendering Readable HTML

Finally, this is a technique to use in situations that require dozens of lines of HTML code being generated dynamically via JavaScript. Take the following example:

var pageContainer = document.getElementById("container");
var pageTitle = "Content Title";
var authorBio = "Mr. Lorum Ipsum";
var pageContent = "Lorum ipsum line text here. Lorum ipsum line text here. 
                   Lorum ipsum line text here. Lorum ipsum line text here.
                   Lorum ipsum line text here. Lorum ipsum line text here. 
                   Lorum ipsum line text here. Lorum ipsum line text here.
                   Lorum ipsum line text here.";
var footerContent = "Copyright 2009";
var HTMLCode = '\n<h1>' + pageTitle + '</h1>\n
               <div id="content">\n
               <p>' + pageContent + '</p>\n
               <div id="author_bio">\n
               <p>' + authorBio +'</p>\n
               </div>\n
               </div>\n
               <div id="footer">
               <p>' + footerContent + '</p>\n
               </div>\n';

pageContainer.innerHTML = HTMLCode;

The line to take note of above is the one that declares the value of the HTMLCode variable. It renders just find in the generated source code, since it utilizes the “new line” character, so it looks like perfectly good HTML. But if this line of code were any longer it would be extremely difficult to read and maintain in the .js file.

Here is the same code as above, but implementing a much more organized method of displaying the HTML:

var pageContainer = document.getElementById("container");
var pageTitle = "Content Title";
var authorBio = "Mr. Lorum Ipsum";
var pageContent = "Lorum ipsum line text here. Lorum ipsum line text here. 
                   Lorum ipsum line text here. Lorum ipsum line text here.
                   Lorum ipsum line text here. Lorum ipsum line text here. 
                   Lorum ipsum line text here. Lorum ipsum line text here.
                   Lorum ipsum line text here.";
var HTMLCode = 	'\n' +
                '<h1>' + pageTitle + '</h1>\n'
                '<div id="content">\n' +
                  '<p>' + pageContent + '</p>\n' +
                  '<div id="author_bio">\n' +
                    '<p>' + authorBio + '</p>\n' +
                  '</div>\n'
		        '</div>\n' +
                '<div id="footer">' +
                  '<p>' + footerContent + '</p>\n' +
                '</div>\n';

pageContainer.innerHTML = HTMLCode;

Now the code is much more readable, and conforms to the manner in which HTML is rendered in an actual HTML page. It even includes proper HTML indenting, and still uses the new line character to properly format the outputted HTML.

Conclusion

Although I didn’t provide a detailed explanation of every concept dealt with in this collection, I hope this list provided beginning and intermediate coders with an overview of a few fairly advanced JavaScript tactics that they can implement in future projects or experiments.

Please feel free to comment on any of the advanced JavaScript concepts I’ve mentioned and some specific ways that you have used them in your own applications.

Related Content

The post 6 Advanced JavaScript Concepts You Should Know appeared first on WebFX Blog.

Queue Laravel Messages (New)

Package:
Queue Laravel Messages
Summary:
Model class for messages to be sent to recipients
Groups:
Databases, Email, Files and Folders, PHP 5, Wireless and Mobile
Author:
Moamen Eltouny
Description:
This package provides a model class for messages to be sent to recipients...

Read more at https://www.phpclasses.org/package/12175-PHP-Model-class-for-messages-to-be-sent-to-recipients.html

PHP Cache Object

Package:
PHP Cache Object
Summary:
Save and load variable values in cache containers
Groups:
Cache, Files and Folders, PHP 5
Author:
Juraj Puchký
Description:
This package can save and load variable values in cache containers...

Read more at https://www.phpclasses.org/package/12167-PHP-Save-and-load-variable-values-in-cache-containers.html#2021-08-06-18:36:23

PHP Read Time

Package:
PHP Read Time
Summary:
Estimate the time for a person to read an article
Groups:
Blogs, PHP 5, Statistics, Text processing
Author:
Waqar Ahmed
Description:
This class can estimate the time for a person to read an article...

Read more at https://www.phpclasses.org/package/12162-PHP-Estimate-the-time-for-a-person-to-read-an-article.html#2021-08-06-05:20:01

Weekly News for Designers № 604

Envato Elements

A framework for building Open Graph images – A look at GitHub’s new Open Graph image generator.
Example from A framework for building Open Graph images

There Is No Such Thing As A CSS Absolute Unit – Learn why CSS units aren’t so absolute after all.
Example from There Is No Such Thing As A CSS Absolute Unit

UI patterns & design inspiration from real products – Browse this curated library of screenshots & interaction patterns from the world’s best digital products.
Example from UI patterns & design inspiration from real products

Learn How to Determine Website Project Requirements Like a Pro – Strategies that can help you paint a clearer picture of what a website project will require.
Example from Learn How to Determine Website Project Requirements Like a Pro

uxbullshit – Read some not-so-recommended UX quotes.
Example from uxbullshit

Image optimisation: the ultimate guide – Learn the ins and outs of getting the best performance from your images.
Example from Image optimisation: the ultimate guide

The State of Developer Ecosystem 2021 – Annual look at the latest trends in the tech industry, as well as interesting facts about tools, technologies, programming languages, and many other facets of the programming world.
Example from The State of Developer Ecosystem 2021

Will Full Site Editing Help WordPress Themes Finally Reach Their Potential? – How Full Site Editing (FSE) could bring a one-size-fits-all approach to WordPress themes.
Example from Will Full Site Editing Help WordPress Themes Finally Reach Their Potential?

Layout with Reveal Animations and Content Preview – Learn to create experimental reveal animations on typographic elements as a repeating pattern.
Example from Layout with Reveal Animations and Content Preview

Prevent unwanted Layout Shifts caused by Scrollbars with the scrollbar-gutter CSS property – How a new CSS property could be a solution to a common problem.
Example from Prevent unwanted Layout Shifts caused by Scrollbars with the scrollbar-gutter CSS property

A Web Designer’s Guide to Working on the Go – Tips for staying productive and secure when you’re away from the office.
Example from A Web Designer’s Guide to Working on the Go

Advanced CSS Gradient Editor – This free CSS design app is full of useful features.
Example from Advanced CSS Gradient Editor

8 CSS & JavaScript Code Examples for Creating Interactive Timelines – Top examples of timelines that are both attractive and engaging.
Example from 8 CSS & JavaScript Code Examples for Creating Interactive Timelines

Project Hunt – A resource for finding the latest no-code projects.
Example from Project Hunt

Is it Time to Ditch the Design Grid? – Why the design grid feels more like a hangover from print than ever.
Example from Is it Time to Ditch the Design Grid?

Thinking About The Cut-Out Effect: CSS or SVG? – Which approach works best for a cut-out effect? Here’s a look at the benefits and drawbacks of each.
Example from Thinking About The Cut-Out Effect: CSS or SVG?

The post Weekly News for Designers № 604 appeared first on Speckyboy Design Magazine.

PHP Print Labels to PDF (New)

640.png
Package:
PHP Print Labels to PDF
Summary:
Generate PDF documents with labels from HTML tags
Groups:
Business, Files and Folders, HTML, PHP 5, Printing
Author:
Rafael Martin Soto
Description:
This package can generate PDF documents with labels from HTML tags...

Read more at https://www.phpclasses.org/package/12173-PHP-Generate-PDF-documents-with-labels-from-HTML-tags.html

How to Provide Great Support to Your Web Design Clients

Building beautiful and functional things is only part of a web designer’s job. Providing maintenance and support for our creations is also a key part of the equation.

This is often the more difficult task. After all, support can be a years-long commitment. Plus, it requires both technical knowledge and people skills – an important combination.

Support also plays a role in developing customer loyalty. Even if you’ve built an amazing website for your client, poor support is likely to damage the relationship. This could be a deciding factor in whether or not they stick with you for the long haul.

So, what goes into providing great support? Let’s look at a few ingredients for keeping your websites running smoothly and your clients happy.

Respond Quickly to Requests

Waiting is no fun – especially when you have a technical issue or question. Therefore, our first rule of client support is to quickly respond to that initial message.

This doesn’t mean you have to resolve things immediately. It’s more of a common courtesy to let your client know that their request has been received. Knowing that they’re on your radar helps to build trust and confidence.

Ideally, a personal response is preferred. Bonus points if it adds any follow-up questions to get the ball rolling on taking care of the request. But even a well-written auto response is acceptable.

The important thing is confirming that you’re aware of their message. That starts the entire support process off on the right foot.

A woman typing on a laptop computer.

Be a Good Listener

When a client approaches you with an issue, it’s easy to jump to conclusions. Sometimes it takes the form of thinking you know the answer before they’ve even finished talking. In other cases, you might be quick to dismiss their claim as a case of simple user error.

It may come from good intentions (or a desire to get back to your other projects), but has the potential to rub your client the wrong way. You might be seen as terse or even rude.

Taking the time to listen is a much more effective approach. While it may eat up a bit more of your schedule, it demonstrates that you care about what your client has to say.

Besides, listening is the best way to gather information. You’ll be less likely to miss out on any important details that will help you resolve the issue. And you can always ask questions afterwards.

Exercising patience has a lot of side benefits. It helps to establish a positive rapport with clients. And they’ll feel more comfortable coming to you when they run into a problem. This is especially important for those who are technically-challenged and intimated by the situation.

A woman having a phone conversation.

Avoid Wild Speculation

Clients come to us because they’re hoping we have answers. In an effort to provide comfort and certainty, web designers may resort to speculation. Taking wild guesses as to what’s causing a specific problem or the timeline for fixing it is meant to bring peace of mind. The reality is that it is more likely to cause frustration if you guessed wrong.

Of course, this doesn’t mean you can’t offer up a laundry list of potential causes. But they should be noted as possibilities – not certainties. And, until a proper diagnosis, timelines should reflect investigating an issue rather than fixing it.

In other words: speak in generalities until you know what you’re dealing with. Don’t let your eagerness to please a client get in the way of finding honest answers.

Immediate solutions aren’t always possible. Thankfully, most people are willing to wait for you to look into an issue or research a question. Give them (and yourself) the chance to do so.

A sign that reads "TRUTH".

Keep the Lines of Communication Open

Complex issues may take a while to resolve. But that doesn’t mean clients should be left in the dark. Periodically checking in with the latest information (even if there aren’t any new developments) goes a long way towards keeping everyone on the same page.

While this may sound relatively simple, it’s not always easy. For example, we web designers often find ourselves acting as liaisons between clients and third-party service providers. If the provider doesn’t keep us in the know, it will make our job that much harder. It can also make us look bad.

Frankly, there’s no need to shield providers from accountability. Thus, if they’re the reason progress is being held up, don’t be afraid to say so. Just keep honesty and fairness in mind when reporting the situation. Painting an accurate picture is still important.

But regardless of who’s in charge of tackling the issue, stay in touch with clients. They must be kept in the loop throughout the entire process.

A paper airplane.

Doing Right by Your Clients

All of these tips can be tied together by a single word: professionalism. It’s something we expect from those who provide us with support. And our clients expect it of us as well.

It requires us to be honest, responsive and willing to go the extra mile. And, above all, a commitment to keeping clients abreast of what’s happening.

Of course, it helps to have loads of technical knowledge. But there’s more to it than knowing code and design. It’s the people skills that enable you to build a solid relationship. And a combination of both is what makes for top-notch support.

You don’t need to be perfect to succeed in this area. Rather, it’s about putting in the effort to take care of your clients. It may just convince them to stick with you for years to come.

The post How to Provide Great Support to Your Web Design Clients appeared first on Speckyboy Design Magazine.

Powered by Gewgley