Phần mềm check shot (Shutter shots) Canon EOS 40D 50D 60D 70D 6D 7D 5D Mark II 5D Mark III 1D Mark IV

Phần mềm check shot (Shutter shots) Canon EOS 40D 50D 60D 70D 6D 7D 5D Mark II 5D Mark III 1D Mark IV giúp mọi người tự check nhanh chóng số ảnh đã chụp của máy ảnh Canon. Nhất là những bạn mua lại máy cũ đã qua sử dụng.

Tải về phần mềm EOSinfo theo link

 https://drive.google.com/file/d/1sk2FvHc05xz8nOgdC03CDIRrSvkGgnZK/view?usp=sharing

Các Camera được hỗ trợ:
Rebel XS 1000D Kiss F
Rebel XSi 450D Kiss X2
Rebel T1i 500D Kiss X3
Rebel T2i 550D Kiss X4
Rebel T3 1100D Kiss X50
Rebel T3i 600D Kiss X5
Rebel T4i 650D Kiss X6i
Rebel T5i 700D Kiss X7i
Rebel SL1 100D Kiss X7
40D 50D 60D 70D
6D 7D

5D Mark II
5D Mark III
1D Mark IV
1D X
ID C


Cách sử dụng: Giải nén và chạy file EOSInfo.exe bằng quyền Administrator

1.Tắt Camera
2.Kết nối Camera qua cổng USB
3.Mở Camera
4.Đóng chương trình EOS Utility nếu đang mở
5.Chờ kết quả
6.Tắt máy ảnh & đọc kết quả

Chúc các bạn thành công

Canon EOS 6D, Lens 40f2.8

Pagination trong ReactJs

Khi làm các ứng dụng web, chúng ta luôn có nhu cầu lấy dữ liệu từ các remote server, API… các dữ liệu mạng xã hội, tin tức, shopping, thanh toán thì rất nhiều records, do đó chúng ta cần giải pháp phân trang để giảm tải sự hiển thị quá nhiều data gây khó chịu cho người dùng.

Hướng dẫn đơn giản sau đây giúp bạn dễ dàng phân trang trong app ReactJs

Ví dụ ở đây sử dụng API dữ liệu các quốc gia, 248 quốc gia tất cả. Chúng ta sẽ bắt đầu với ứng dụng react đơn giản.

Tạo một app react js

npx create-react-js pagination

Cài đặt các thư viện cần thiết

npm i bootstrap countries-api prop-types react-flags

Sau khi cài đặt thành công chúng ta copy folder flags ở địa chỉ node_modules\react-flags\vendor đưa vào folder public\img

Open file index.js, thêm code bootstrap

import "bootstrap/dist/css/bootstrap.min.css";

Components

Trong folder src, tạo folder components

Tạo 2 component

  • CountryCard.jsx Component
  • Pagination.jsx Component

CountryCard Component

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import Flag from 'react-flags';

class CountryCard extends Component {
    render() {
        const { cca2: code2 = "", region = null, name = {} } = this.props.country || {};
        return (
            <Fragment>
                <div className="col-sm-6 col-md-4 country-card">
                    <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
                        <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                            <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
                        </div>
                        <div className="px-3">
                            <span className="country-name text-dark d-block font-weight-bold">
                                {name.common}
                            </span>
                            <span className="country-region text-secondary text-uppercase">
                                {region}
                            </span>
                        </div>
                    </div>
                </div>
            </Fragment>
        )
    }
}

CountryCard.propTypes = {
    country: PropTypes.shape({
        cca2: PropTypes.string.string,
        region: PropTypes.string.isRequired,
        name: PropTypes.shape({
            common: PropTypes.string.isRequired
        }).isRequired
    }).isRequired
}
export default CountryCard

Pagination Component

 
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";

const LEFT_PAGE = "LEFT";
const RIGHT_PAGE = "RIGHT";

const range = (from, to, step = 1) => {
  let i = from;
  const range = [];

  while (i <= to) {
    range.push(i);
    i += step;
  }

  return range;
};

class Pagination extends Component {
  constructor(props) {
    super(props);
    const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;

    this.pageLimit = typeof pageLimit === "number" ? pageLimit : 30;
    this.totalRecords = typeof totalRecords === "number" ? totalRecords : 0;

    this.pageNeighbours =
      typeof pageNeighbours === "number"
        ? Math.max(0, Math.min(pageNeighbours, 2))
        : 0;

    this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);

    this.state = { currentPage: 1 };
  }

  componentDidMount() {
    this.gotoPage(1);
  }

  gotoPage = page => {
    const { onPageChanged = f => f } = this.props;

    const currentPage = Math.max(0, Math.min(page, this.totalPages));

    const paginationData = {
      currentPage,
      totalPages: this.totalPages,
      pageLimit: this.pageLimit,
      totalRecords: this.totalRecords
    };

    this.setState({ currentPage }, () => onPageChanged(paginationData));
  };

  handleClick = (page, evt) => {
    evt.preventDefault();
    this.gotoPage(page);
  };

  handleMoveLeft = evt => {
    evt.preventDefault();
    this.gotoPage(this.state.currentPage - this.pageNeighbours * 2 - 1);
  };

  handleMoveRight = evt => {
    evt.preventDefault();
    this.gotoPage(this.state.currentPage + this.pageNeighbours * 2 + 1);
  };

  fetchPageNumbers = () => {
    const totalPages = this.totalPages;
    const currentPage = this.state.currentPage;
    const pageNeighbours = this.pageNeighbours;

    const totalNumbers = this.pageNeighbours * 2 + 3;
    const totalBlocks = totalNumbers + 2;

    if (totalPages > totalBlocks) {
      let pages = [];

      const leftBound = currentPage - pageNeighbours;
      const rightBound = currentPage + pageNeighbours;
      const beforeLastPage = totalPages - 1;

      const startPage = leftBound > 2 ? leftBound : 2;
      const endPage = rightBound < beforeLastPage ? rightBound : beforeLastPage;

      pages = range(startPage, endPage);

      const pagesCount = pages.length;
      const singleSpillOffset = totalNumbers - pagesCount - 1;

      const leftSpill = startPage > 2;
      const rightSpill = endPage < beforeLastPage;

      const leftSpillPage = LEFT_PAGE;
      const rightSpillPage = RIGHT_PAGE;

      if (leftSpill && !rightSpill) {
        const extraPages = range(startPage - singleSpillOffset, startPage - 1);
        pages = &#91;leftSpillPage, ...extraPages, ...pages&#93;;
      } else if (!leftSpill && rightSpill) {
        const extraPages = range(endPage + 1, endPage + singleSpillOffset);
        pages = &#91;...pages, ...extraPages, rightSpillPage&#93;;
      } else if (leftSpill && rightSpill) {
        pages = &#91;leftSpillPage, ...pages, rightSpillPage&#93;;
      }

      return &#91;1, ...pages, totalPages&#93;;
    }

    return range(1, totalPages);
  };

  render() {
    if (!this.totalRecords) return null;

    if (this.totalPages === 1) return null;

    const { currentPage } = this.state;
    const pages = this.fetchPageNumbers();

    return (
      <Fragment>
        <nav aria-label="Countries Pagination">
          <ul className="pagination">
            {pages.map((page, index) => {
              if (page === LEFT_PAGE)
                return (
                  <li key={index} className="page-item">
                    <a
                      className="page-link"
                      href="#"
                      aria-label="Previous"
                      onClick={this.handleMoveLeft}
                    >
                      <span aria-hidden="true">&laquo;</span>
                      <span className="sr-only">Previous</span>
                    </a>
                  </li>
                );

              if (page === RIGHT_PAGE)
                return (
                  <li key={index} className="page-item">
                    <a
                      className="page-link"
                      href="#"
                      aria-label="Next"
                      onClick={this.handleMoveRight}
                    >
                      <span aria-hidden="true">&raquo;</span>
                      <span className="sr-only">Next</span>
                    </a>
                  </li>
                );

              return (
                <li
                  key={index}
                  className={`page-item${
                    currentPage === page ? " active" : ""
                  }`}
                >
                  <a
                    className="page-link"
                    href="#"
                    onClick={e => this.handleClick(page, e)}
                  >
                    {page}
                  </a>
                </li>
              );
            })}
          </ul>
        </nav>
      </Fragment>
    );
  }
}

Pagination.propTypes = {
  totalRecords: PropTypes.number.isRequired,
  pageLimit: PropTypes.number,
  pageNeighbours: PropTypes.number,
  onPageChanged: PropTypes.func
};

export default Pagination;

App.js

import React, { Component } from 'react';
import Countries from 'countries-api/lib/data/Countries.json';
import './App.css';
import Pagination from "./components/Pagination";
import CountryCard from './components/CountryCard';
class App extends Component {
  state = {
    allCountries: [],
    currentCountries: [],
    currentPage: null,
    totalPages: null
  };

  componentDidMount() {
    const allCountries = Countries;
    this.setState({ allCountries });
  }

  onPageChanged = data => {
    const { allCountries } = this.state;
    const { currentPage, totalPages, pageLimit } = data;

    const offset = (currentPage - 1) * pageLimit;
    const currentCountries = allCountries.slice(offset, offset + pageLimit);

    this.setState({ currentPage, currentCountries, totalPages });
  };


  mapCountry(country, index) {
    return <CountryCard key={country.cca3} country={country} />
  }
  render() {
    const { allCountries, currentCountries, currentPage, totalPages } = this.state;
    const totalCountries = allCountries.length;

    if (totalCountries === 0) return null;

    const headerClass = [
      "text-dark py-2 pr-4 m-0",
      currentPage ? "border-gray border-right" : ""
    ]
      .join(" ")
      .trim();

    const displayCountries = currentCountries.map(this.mapCountry);
    return (
      <div className="container mb-5">
        <div className="row d-flex flex-row py-5">
          <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">

            <div className="d-flex flex-row align-items-center">
              <h2 className={headerClass}>
                <strong className="text-secondary">{totalCountries}</strong>{" "}
                Countries
              </h2>
              {currentPage && (
                <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                  Page <span className="font-weight-bold">{currentPage}</span> /{" "}
                  <span className="font-weight-bold">{totalPages}</span>
                </span>
              )}
            </div>
            <div className="d-flex flex-row py-4 align-items-center">
              <Pagination
                totalRecords={totalCountries}
                pageLimit={18}
                pageNeighbours={1}
                onPageChanged={this.onPageChanged}
              />
            </div>
          </div>
          {displayCountries}
        </div>
      </div>
    );
  }
}

export default App;


Download mã nguồn ở đây

GitHub

LÀM ĐÀN ÔNG ĐỪNG CÁI GÌ CŨNG CỐ GIỎI, CHỈ CẦN XUẤT CHÚNG MỘT KỸ NĂNG, CẢ ĐỜI BẠN SẼ NỞ HOA

Người xưa đã có câu “nhất nghệ tinh, nhất thân vinh, hay một nghề cho chín, còn hơn chín nghề.”  Nhưng đọc thêm bài viết dài một chút cũng không hẳn là một ý tồi.

Đứa bạn 1: Tôi có thể thiết kế rất giỏi

Đứa bạn 2: Tôi là một huấn luyện viên gym rất xuất sắc

Đồng nghiệp 1: Tôi cực kỳ giỏi việc nịnh sếp

Đồng nghiệp 2: Tôi thực sự rất giỏi việc đóng clip hài

Tôi: Tôi có thể làm mọi thứ mà các ông làm

1. Có một sự thật là khi bạn giỏi mọi thứ, thực ra bạn lại chẳng thực sự giỏi một thứ gì. Ngoại trừ những “siêu nhân” biết tuốt, mỗi người trên cuộc đời này chỉ thực sự vĩ đại trong một lĩnh vực hay kỹ năng gì đó, và đó là thứ sẽ nuôi sống người đàn ông cả cuộc đời.

2. Trường học dạy chúng ta phải có điểm cao ở mọi môn, nhưng trường đời chỉ thực sự cần bạn là chuyên gia trong một lĩnh vực mà thôi. Như các cụ đã nói, một nghề cho chín còn hơn chín nghề.

3. Trong cuốn sách Average is Over (Tạm dịch: Sự chấm hết của trung bình), kinh tế gia Tyler Cowen cho rằng kỷ nguyên của sự “bình thường” sắp chấm dứt.

Chỉ vào chục năm nữa, khi robot bắt đầu thay thế các công việc đòi hỏi kỹ năng tầm thấp hoặc trung bình, chiến lược sống sót duy nhất của bạn là phải giỏi ở một thứ, chỉ cần một mà thôi.

5. Giỏi nhiều thứ mới dễ, xuất chúng ở một ngành nghề mới thực sự khó. Khi bạn nghe thấy một đứa bạn, vừa đánh đàn giỏi, vừa học giỏi, vừa kinh doanh giỏi, vừa làm chồng giỏi, vừa làm bố giỏi… nói chung cái gì cũng giỏi, thì tốt nhất hãy xem gia thế của bạn ấy là ai trước.

Vì sự thực là, sẽ luôn có chi phí cơ hội, cực kỳ hiếm người có thể xuất sắc ở mọi lĩnh vực. Ngoài ra, thì để cái gì cũng biết thì vô cùng dễ, để thực sự là chuyên gia đầu ngành của một lĩnh vực thì đòi hỏi sức kiên trì lớn hơn rất nhiều.

6. Quy luật 10,000 giờ. Để thực sự xuất chúng ở một chuyên ngành nào đó, bạn cần bỏ ra ít nhất 10,000 giờ cho chúng. Nghe thì không to lắm, nhưng để ngày nào cũng bỏ ra 2-3 tiếng “luyện công” thì bạn thực sự cần có kỹ năng kỷ luật bản thân rất tốt. Nhưng biết làm sao được, nếu ghế của cha bạn không to, nhà bạn không giàu, thì đó dường như là con đường duy nhất dẫn đến thành công.

7. “Tạm được là chưa đủ.” Chắc chắc đôi khi bạn sẽ gặp những sếp dễ tính, và khi bạn nộp một sản phẩm chưa hoàn hảo nhất với sức lực của mình, họ đã khen “Em làm tốt rồi”. Cái lợi của việc này là bạn cảm thấy được trân trọng, nhưng thực ra ông sếp đang làm hại bạn.

Thỏa mãn bản thân với những kết quả chưa thực sự “chín” sẽ làm bạn mất thái độ cầu toàn, và khó có thể leo lên tầm chuyên gia, khi những việc nhỏ còn làm chưa trọn vẹn.

8. Kiên nhẫn không phải dễ dàng. Nó đau đớn và cực kỳ nhọc nhằn. Thành công chưa bao giờ là dễ. Tập trung vào một điểm mạnh của mình và đưa nó lên “cấp” cao nhất chưa bao giờ là dễ dàng. Bạn sẽ gặp khó khăn, bạn sẽ nản và muốn nhảy vòng quanh để giỏi ở mọi thứ, nhưng đó là cái bẫy.

Thời trai trẻ, đừng lãng phí.

Từ năm 20-30 tuổi, siêu tập trung vào chỉ một lĩnh vực, trở thành chuyên gia hàng đầu trong lĩnh vực đó, ngành nghề gì cũng được, và bạn sẽ không phải hối hận.

Nguồn: Tri thức trẻ
Ảnh: happy.live

Sử dụng cache trong ASP.NET MVC với Redis Cache, Memory Cache

Khi lập trình ứng dụng chúng ta hay gặp vấn đề làm sao cho tối ưu tài nguyên của hệ thống, tăng tốc độ trải nghiệm từ phía end user. Đặc biệt là các ứng dụng web.

Cache là gì ? 
Lý thuyết: Cache là tên gọi của bộ nhớ đệm – nơi lưu trữ các dữ liệu nằm chờ các ứng dụng hay phần cứng xử lý. Mục đích của nó  để tăng tốc độ xử lý (có sẵn xài liền không cần tốn thời gian đi lùng sục tìm kéo về).

Thực tế: Cache là các dữ liệu trong phiên làm việc trước của các ứng dụng, chương trình mà hệ điều hành lưu lại nhằm giúp việc tải data trong các phiên làm việc sau được nhanh hơn.

Tip này hướng dẫn các bạn cách implement cache vào ứng dụng, sử dụng thư viện đã được wrap lại từ Memery Cache & Redis Cache

Đầu tiên tải về thư viện tại Nuget: https://www.nuget.org/packages/colorlife.tuanitpro.com/

Hoặc cài đặt trực tiếp trong Visual Studio

Tạo class Customer để test

public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Sử dụng Memory Cache

  private static void MemoryCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory();
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "Memory_Cache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
             
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Get Customer In MemoryCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Sử dụng Radis Cache
Đầu tiên tải Redis cho Windows & cài đặt tại: https://github.com/MicrosoftArchive/redis/releases

Code:

  private static void RedisCache_Customer_Test()
        {
            ICacheProviderFactory cacheProvideractory = new CacheProviderFactory(new CacheProviderOptions
            {
                SlidingExpiration = TimeSpan.FromSeconds(30),
                ConnectionString = "localhost:6379" // mặc định
            }, CacheType.Redis);
            IDataCacheProvider dataCacheProvider = cacheProvideractory.CreateFactory();
            Customer customer = new Customer
            {
                FirstName = "Tuan",
                LastName = "Le"
            };
            string cacheKey = "RedisCache_Customer";
            dataCacheProvider.Set(cacheKey, customer);
            Console.WriteLine("Before cache: " + customer.FirstName + " " + customer.LastName);
            Thread.Sleep(5000);
            customer.LastName = "Le_Updated";
            Console.WriteLine("After Update: " + customer.FirstName + " " + customer.LastName);

            var customerInCache = dataCacheProvider.Get(cacheKey);

            Console.WriteLine("Customer In RedisCache: " + customerInCache.FirstName + " " + customerInCache.LastName);
        }

Thư viện còn hỗ trợ một số chức năng nho nhỏ khác, sẽ nói trong bài viết khác. Chúc các bạn thành công 🙂

Tạo bot discord check giá coinmarketcap

Các thư viện sử dụng

https://discord.js.org

https://www.npmjs.com/package/node-fetch

Yêu cầu máy đã cài nodejs

Tạo app trên discord: https://discordapp.com/developers/docs/intro

Lấy token để sử dụng sau:

Sử dụng Visual Code để code.

Tạo file bot.js

Code:

npm install discord.js

const Discord = require('discord.js');
const client = new Discord.Client();

const commandPrefix = "!";

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

client.on('message', (message)=> {
    if(!message.content.startsWith(commandPrefix)) return;
    const args = message.content.slice(commandPrefix.length).trim().split(/ +/g);
    const command = args.shift().toLowerCase();
       
    switch(command){
    	case "hello":
			hello(message);
    	break;
    	case "help":
			help(message);
    	break;
    	case "ping":
			sendText(message, "Pong");
    	break;
    	case "clear":
    		clear(message);
    	break;
    	case "cmc":
    		coinmarketcap(message);
    	break;
    	case "c":
    		chart(message, args);
    	break;
    	case "p":
    		price(message, args);
    	break;    	
    	default:
    		sendText(message, "Command not found.")
    	break;
    }
});

client.login('NDQyMDAyMTAyODAyMzE3MzMz.DdMfbw.3eV-Qk5N2TpDIDgatQb1ILeD0Hc');

Các hàm chính trong bot

function hello(message){
	message.channel.send("Hello " + message.author + "! Nice to meet you. :smiley: ");
}
function help(message){
	 let embed = new Discord.RichEmbed()            
            .setAuthor("Hello world", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("!hello", "Sends a friendly message!")
            .addField("!help", "Sends this help embed")
            .addField("!cmc", "Coin Market Cap")
            .addField("!ping", "Ping")
            .addField("!p", "Price of coin. Ex: BTCUSDT or price BTC")
            .setTitle("Bot commands:")
            .setFooter("Here you have all bot commands you can use!")
            .setColor("AQUA");        
        message.channel.send({embed: embed});
}
function sendText(message, text){
	message.channel.send(text).then(msg=>{msg.delete(10000)}); 
    message.delete(12000);
}

function clear(message){
	if (message.member.hasPermission("MANAGE_MESSAGES")) {
        message.channel.fetchMessages()
           .then(function(list){
                message.channel.bulkDelete(list);
            }, function(err){message.channel.send("ERROR: ERROR CLEARING CHANNEL.")})                        
    }
    else{
    	console.log("You don't have permission");
    }

Hàm lấy giá trên coinmarketcap.com

npm install node-fetch

function coinmarketcap(message){
	    let url = 'https://api.coinmarketcap.com/v2/global/';	
		let bitcoin_percentage_of_market_cap='';
		let total_market_cap= '';
		let total_volume_24h = '';
		let last_updated = '';
		fetch(url)
    	.then(res => res.json())
    	.then(json => {
    		// console.log(json);
    		bitcoin_percentage_of_market_cap =  json.data.bitcoin_percentage_of_market_cap;
    		total_market_cap = json.data.quotes.USD.total_market_cap;
 			total_volume_24h = json.data.quotes.USD.total_volume_24h;
 			last_updated = json.data.last_updated;
			
 			let embed = new Discord.RichEmbed()
 			.setAuthor("Coinmarketcap", "http://icons.iconarchive.com/icons/froyoshark/enkel/256/Bitcoin-icon.png")
            .addField("Market Cap (USD):", numberFormat(total_market_cap))
            .addField("24h Vol (USD): ", numberFormat(total_volume_24h))
            .addField("BTC Dominance(%): ", bitcoin_percentage_of_market_cap)            
            .setFooter("Last Updated: " + timeConverter(last_updated))
            .setColor("AQUA");

        // Send the embed with message.channel.send()
        message.channel.send({embed: embed}).then(msg=>{msg.delete(10000)}); 
        message.delete(12000);       
    	});    	 
}

Thêm bot vào Server Discord

Open link & thay bằng client_id của bạn trên trình duyệt

Khởi chạy bot

node bot.js

Demo

 

Source code:  GitHub

Chúc các bạn thành công

DONATE

Bitcoin address: 1tmxPQbmycQMehAAZ8FshbvEoQZ6Ri7PT

ETH Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

ETC Address: 0xfe17aaf16bceb4311795b2e8ff0199640bdce54a

XVG Address: D9iP7fhKbHJKViwUG9MWcAt3JPdCuxnCZJ

binance price alert, coin watchlists using c#

Ứng dụng theo dõi giá của coin trên sàn Binance & thông báo khi đạt mức giá quy định.

Lấy danh sách coin trên Binance

var listSymbol = new List();
            using (var client = new BinanceClient())
            {
                var rs = await client.GetAllPricesAsync();
                if (rs.Success && rs.Data != null)
                {
                    foreach (var item in rs.Data)
                    {
                        listSymbol.Add(new SelectListItem
                        {
                            Value = item.Symbol,
                            Text = item.Symbol + " " + item.Price
                        });
                    }
                }
            }

 

Tham khảo: https://github.com/JKorf/Binance.Net

Source code: https://github.com/tuanitpro/binance-price-alert

Hướng dẫn tạo Toggle (Collapsible) ẩn/hiện trong Angular 4

Khi thiết kế website chúng ta hay có nhu cầu ẩn hiện một div / panel, hay một vùng dữ liệu nào đó. Khi người dùng bấm vào sẽ hiển thị nội dung, giúp tiết kiệm không gian hiển thị.

Link demo trên W3school, sử dụng Bootstrap. Link

Trong phạm vi bài viết này chúng ta sẽ thực hiện nó trên angular4. Chúng ta sẽ viết một component để tùy ý sử dụng, viết một lần dùng ở nhiều nơi.

Demo 

Tạo  component collapsible.component với 2 file là collapsible.component.html & collapsible.component.ts

Code file HTML

<section id="tuanitpro_collapse">
  <div class="filter">
    <a (click)="toggleCollapse()" title="Click để đóng mở">{{title || 'Tìm kiếm'}}
    <i class="fa fa-times" *ngIf="!isCollapseOpen"></i>
    <i class="fa fa-search" *ngIf="isCollapseOpen"></i>
  </a>
  </div>
  <div class="filter_panel" [hidden]="isCollapseOpen">
    <div class="x_panel">
      <div class="x_content">
        <ng-content></ng-content>
      </div>
    </div>
  </div>
</section>

Code file .ts

import { Component, OnInit, Input, AfterViewInit, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-collapsible',
  templateUrl: './collapsible.component.html'
})
export class CollapsibleComponent implements OnInit, AfterViewInit {
  @Input() title: string;
  @Input() uid: string;

  public isCollapseOpen = true;
  constructor(private ref: ChangeDetectorRef) { }

  ngOnInit() {
  }
  ngAfterViewInit() {
    const savedState = localStorage.getItem(this.uid + '_filterKey');
    if (savedState) {
      if (savedState === 'true') {
        this.isCollapseOpen = true;
      } else {
        this.isCollapseOpen = false;
      }
    }
    this.ref.detectChanges();

  }
  toggleCollapse() {
    const currentState = this.isCollapseOpen;
    this.isCollapseOpen = !currentState;
    localStorage.setItem(this.uid + '_filterKey', String(this.isCollapseOpen));
  }
}

Cách dùng:

import vào app.module.ts

import { CollapsibleComponent } from ‘./collapsible.component’;
@NgModule({
declarations: [
CollapsibleComponent
],
Trong UI cần ẩn hiện
<app-collapsible title="Tiêu đề" uid="category">
<div class="panel">
Nội dung cần ẩn hiện
</div>
</app-collapsible>

Chúc các bạn thành công.